R DATA VISUALIZATION

Why Data Visualization?

Raw data points don’t provide much insight to kick off data analysis.

Data Visualization is brilliant in * exploring the pattern of data briefly at the early stage * in the final conclusion, enhance the story telling of data analysis

Reminder on Workflow Again

R Data Science Workflow Like the picture above shows, in the stage of Understand (exploring the data sets), Transform, Visualize and Model are used in an iterative manner so as to get the best early understaning about the data sets.

Built-in Plot Functions

The advantage of using built-in plotting utilities is they are easy.

It let you quickly visualize the data pattern while you are trying to gain a brief insight at the early stage of your workflow.

plot(iris)

Refresh on Built-in Plot Tools

For built-in R data visualization, go to the R Intro project on Github to refresh your memory (R Intro Source Codes)[https://github.com/ngsanluk/R-Intro]

Grammar of Graphics: ggplot2

If the built-in plotting tools are not enough for you, go for ggplot2. It is the most popular data visualization for R.

ggplot2 is an open-source data visualization package for R. ggplot2 breaks up graphs into semantic components such as scales and layers. Since 2005, ggplot2 has grown in use to become one of the most popular R packages.

ggplot2 Cheat Sheet

ggplot 2 cheat sheet


INSTALL / LOAD PACKAGES

if (!require("pacman")) install.packages("pacman") # check if pacman already installed. If not, install it.
Loading required package: pacman
pacman::p_load(
  pacman, # package manager
  datasets, # built-in data sets
  rio, # r input / output
  magrittr, # for piping commands
  tidyverse, 
  modelr # mathematics model
) # Load required packages.  If they are NOT already downloaded, download them automatically.

BASIC GRAMMAR

ggplot2 is based on the grammar of graphics, the idea that you can build every graph from the same components below:

a data set + a coordinate system + and geom—visual that represent data points

Grammar of Graphics

Use Built-in Data Sets

Let’s use the built-in cars data set for a simple ggplot

print(cars)
cars %>% ggplot() 
Error in cars %>% ggplot() : could not find function "%>%"

geom_point() function

It’s easy to add geometry layer to the base co-ordinate Let’s ADD a layer of data points using geom_point() function.

And Yes, you can ADD a layer by using the + operator We use geom_point() function that requires x and y value for each geom point. In 2D co-ordinate, a point is described by its x and y value.

We need to provide a mapping that specifies the data columns’ name to map to a point’s x and y value

That mapping is defined by an aesthetics function: aes()

We usually name the plot by the name of geom that we use to represent data. In this case, it’s widely called Scatter Plot. It is useful to explore the relation of two variables.

cars %>% ggplot() + # NOTE: '+' operator must be placed at the end of a line
  geom_point(mapping = aes(x=speed, y=dist))

Use geom_line() to replace geom_point()

geom_point() and geom_line() require very similar parameters. geom_line() is simply an enhanced visualization that automatically connect all the points. Line chart are used to explore/emphasize trending.

# just change geom_point to geom_line without change anything else
cars %>% ggplot() +
  geom_line(mapping = aes(x=speed, y=dist)) 

Use geom_smoth() to project a smooth line

geom_smooth() and geom_point() require very similar parameters. geom_smooth() smooths out the line progression

# just change geom_point to geom_line without change anything else
cars %>% ggplot() +
  geom_smooth(mapping = aes(x=speed, y=dist)) 
`geom_smooth()` using method = 'loess' and formula 'y ~ x'

Geometric Objects Comparison

a geom is the geometrical object that a plot use to represent data. We used points, lines and smooth above on the same data set and they provide very different message.

We usually name the plot by the name of geom that we use to represent data.

Adding Multiple Layers

# just change geom_point to geom_line without change anything else
cars %>% ggplot() +
  geom_point(mapping = aes(x=speed, y=dist)) +
  geom_smooth(mapping = aes(x=speed, y=dist)) 
`geom_smooth()` using method = 'loess' and formula 'y ~ x'

Adding Aesthetics to your Plots

Un-comment the extra parameters to add more aesthetics to your plot

cars %>% ggplot() +
  geom_point(mapping = aes(x=speed, y=dist),
             color = "orange", # the color of data points
             # size = 3, # the size of data point
             # alpha = 0.5, # the transparency of data points, min=0, max=1
             # shape = 0, # the shape of data point
             )


PLOT WITH OUR OWN DATA

Loading Data: allowance

allowance = read_csv("./data/allowance.csv")
Rows: 11 Columns: 13
── Column specification ────────────────────────────────────────────────────────────
Delimiter: ","
chr  (2): Assessment_Year, Personal_Disability_Allowance
dbl (11): Basic, Married_Person, Child, Child_newborn, Dependent_Brother_Sister,...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
print(allowance)

Allowance Data Set in Simple Scatterplot

allowance %>% 
  ggplot() + 
  geom_point(
    mapping=aes(x=Assessment_Year, y=Basic),
    color = "orange",
    size = 3
    ) 

Continuous Values vs. Discrete Values

Continuous values refer to numbers value that has wide range. E.g. salary, height.

Discrete values refer to a limited number of valid values. It can be string. It can be a few distinct numbers.

When you produce plots, pay attention to what type of value are required by the geom object.

In many cases, you will need to convert the data first. mutate() function are quite often used for that.

example:

allowance = allowance %>% 
  mutate(Assessment_Year = as.numeric(substr(Assessment_Year, 1 ,4))) 

Simple Line Plot

Adding Multiple Layers of Geometry

allowance %>% ggplot() +
  geom_line(mapping = aes(x=Assessment_Year, y=Basic, group=1, color="Orange")) +
  geom_point(mapping = aes(x=Assessment_Year, y=Basic, group=1, color="Orange"))


# As both geom use the same data mapping, the above statements can be simplified as
allowance %>% ggplot(aes(x=Assessment_Year, y=Basic, group=1, color="Orange")) +
  geom_line() +
  geom_point(size=5)

Use geom_smooth() to smooth out the line

allowance %>% ggplot(aes(x=Assessment_Year, y=Basic, group=1, color="Orange")) +
  geom_smooth() +
  geom_point(size=5)
`geom_smooth()` using method = 'loess' and formula 'y ~ x'

Save a Plot: ggsave()

my.first.plot = allowance %>% 
  ggplot() + 
  geom_point(
    mapping=aes(x=Assessment_Year, y=Basic),
    color = "orange",
    size = 3,
    ) 

print(my.first.plot)

ggsave("./output/my_first_plot.png") # default image size
Saving 6.62 x 4.09 in image
ggsave("./output/my_first_plot_large.png", width=4000, height=2000, unit="px")

Bar Chart with geom_col()

allowance %>% 
  ggplot() +
  geom_col(mapping=aes(x=Assessment_Year, y=Basic),
           fill="tomato") 

geom_bar()

geom_bar() is used for counting the frequency of each occurrence of observed value. It’s usually for counting a limit set of values

allowance %>% 
  ggplot() +
  geom_bar(mapping=aes(x=Basic))  


CHALLENGE

Multiple Layers of Lines

add line plot for column of ‘Child’ in the same plot, add another line plot for ‘Dependent_Parent_60’


WORK WITH MORE COMPLEX DATA

Loading Data: graduates.csv

graduates = read_csv("./data/graduates.csv")
Rows: 653 Columns: 5
── Column specification ────────────────────────────────────────────────────────────
Delimiter: ","
chr (4): AcademicYear, LevelOfStudy, ProgrammeCategory, Sex
dbl (1): Headcount

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
print(graduates)

Simple Scatterplot

Let’s explore the data with some simple ggplot plot. Just some quick exploration. They might not be very useful.

Use filter() to Extract Required Rows

CHALLENGE

Comparison with Line Plots

Use line plots to compare female undergraduate students headcount trending in “ProgrammeCategory” of “Business and Management” and “Engineering and Technology” Use filter() to extract required records You can use multiple filter() call Use &, | or multiple conditions

graduates %>% 
  .$ProgrammeCategory %>% # . means the parameter from previous command
  unique() # display the unique names of ProgrammeCategory
[1] "Arts and Humanities"            "Business and Management"       
[3] "Education"                      "Engineering and Technology"    
[5] "Medicine, Dentistry and Health" "Sciences"                      
[7] "Social Sciences"               
graduates %>% 
  filter(LevelOfStudy=="Undergraduate", Sex=="F") %>%
  filter(ProgrammeCategory=="Business and Management" | ProgrammeCategory=="Engineering and Technology") %>% 
  print() # Test extracting and printing the required records.
graduates %>% 
  filter(LevelOfStudy=="Undergraduate", Sex=="F") %>%
  filter(ProgrammeCategory=="Business and Management" | ProgrammeCategory=="Engineering and Technology") %>% 
  ggplot(
    aes(x=AcademicYear, 
             y=Headcount,
             group=ProgrammeCategory, 
             color=ProgrammeCategory
             )
    ) +
    geom_line() +
    geom_point() 

NA

CHALLENGE: line plot for hibor_fixing_1m

library(jsonlite) # load package

Attaching package: ‘jsonlite’

The following object is masked from ‘package:purrr’:

    flatten
hkma.interbank.url = "https://api.hkma.gov.hk/public/market-data-and-statistics/daily-monetary-statistics/daily-figures-interbank-liquidity"
interbank.liquidity = fromJSON(hkma.interbank.url)
# the above retrieval will take a while.  The server response is slow.
summary(interbank.liquidity)
       Length Class  Mode
header 3      -none- list
result 2      -none- list
str(interbank.liquidity)
List of 2
 $ header:List of 3
  ..$ success : logi TRUE
  ..$ err_code: chr "0000"
  ..$ err_msg : chr "No error found"
 $ result:List of 2
  ..$ datasize: int 100
  ..$ records :'data.frame':    100 obs. of  44 variables:
  .. ..$ end_of_date                                    : chr [1:100] "2022-04-11" "2022-04-08" "2022-04-07" "2022-04-06" ...
  .. ..$ cu_weakside                                    : num [1:100] 7.85 7.85 7.85 7.85 7.85 7.85 7.85 7.85 7.85 7.85 ...
  .. ..$ cu_strongside                                  : num [1:100] 7.75 7.75 7.75 7.75 7.75 7.75 7.75 7.75 7.75 7.75 ...
  .. ..$ disc_win_base_rate                             : num [1:100] 0.75 0.75 0.75 0.75 0.75 0.75 0.75 0.75 0.75 0.75 ...
  .. ..$ hibor_overnight                                : num [1:100] 0.03 0.02 0.02 0.02 0.01 0.02 0.03 0.03 0.02 0.02 ...
  .. ..$ hibor_fixing_1m                                : num [1:100] 0.188 0.185 0.188 0.196 0.206 ...
  .. ..$ twi                                            : num [1:100] 96 95.8 95.6 95.7 95.6 95.5 95.2 95.3 95.7 95.9 ...
  .. ..$ opening_balance                                : int [1:100] 337554 337554 337554 337551 337551 337551 337551 337534 337534 337534 ...
  .. ..$ closing_balance                                : int [1:100] 337554 337554 337554 337554 337551 337551 337551 337551 337534 337534 ...
  .. ..$ market_activities                              : chr [1:100] "+0" "+0" "+0" "+0" ...
  .. ..$ interest_payment                               : chr [1:100] "+0" "+0" "+0" "+3" ...
  .. ..$ discount_window_reversal                       : chr [1:100] "-0" "-0" "-0" "-0" ...
  .. ..$ discount_window_activities                     : chr [1:100] "+0" "+0" "+0" "+0" ...
  .. ..$ intraday_movements_of_aggregate_balance_at_0930: int [1:100] 371125 375503 377226 375387 381938 371916 363729 363880 350696 357104 ...
  .. ..$ intraday_movements_of_aggregate_balance_at_1000: int [1:100] 375339 379266 385469 381909 386912 373675 367985 366723 358279 358428 ...
  .. ..$ intraday_movements_of_aggregate_balance_at_1100: int [1:100] 398461 398028 395229 406391 391314 395677 388202 383598 376635 375870 ...
  .. ..$ intraday_movements_of_aggregate_balance_at_1200: int [1:100] 404220 404131 403552 353051 404440 399350 402104 331056 383952 385132 ...
  .. ..$ intraday_movements_of_aggregate_balance_at_1500: int [1:100] 402494 400980 401072 348658 405804 398916 400683 334817 391180 389282 ...
  .. ..$ intraday_movements_of_aggregate_balance_at_1600: int [1:100] 403079 400951 402067 353770 408927 399310 404832 337197 392468 391754 ...
  .. ..$ forex_trans_t1                                 : chr [1:100] "+0" "+0" "+0" "+0" ...
  .. ..$ other_market_activities_t1                     : chr [1:100] "+0" "+0" "+0" "+0" ...
  .. ..$ reversal_of_discount_window_t1                 : chr [1:100] "-0" "-0" "-0" "-0" ...
  .. ..$ interest_payment_issuance_efbn_t1              : chr [1:100] "+0" "+0" "+0" "+0" ...
  .. ..$ forecast_aggregate_bal_t1                      : int [1:100] 337554 337554 337554 337554 337554 337551 337551 337551 337551 337534 ...
  .. ..$ forex_trans_t2                                 : chr [1:100] "+0" "+0" "+0" "+0" ...
  .. ..$ other_market_activities_t2                     : chr [1:100] "+0" "+0" "+0" "+0" ...
  .. ..$ reversal_of_discount_window_t2                 : chr [1:100] "-0" "-0" "-0" "-0" ...
  .. ..$ interest_payment_issuance_efbn_t2              : chr [1:100] "+32" "+0" "+0" "+0" ...
  .. ..$ forecast_aggregate_bal_t2                      : int [1:100] 337586 337554 337554 337554 337554 337534 337551 337551 337551 337541 ...
  .. ..$ forex_trans_t3                                 : chr [1:100] "+0" "+0" "+0" "+0" ...
  .. ..$ other_market_activities_t3                     : chr [1:100] "+0" "+0" "+0" "+0" ...
  .. ..$ reversal_of_discount_window_t3                 : chr [1:100] "-0" "-0" "-0" "-0" ...
  .. ..$ interest_payment_issuance_efbn_t3              : chr [1:100] "+0" "+29" "+0" "+0" ...
  .. ..$ forecast_aggregate_bal_t3                      : int [1:100] 337586 337583 337554 337554 337554 337534 337534 337551 337551 337541 ...
  .. ..$ forex_trans_t4                                 : chr [1:100] NA NA NA NA ...
  .. ..$ other_market_activities_t4                     : chr [1:100] NA NA NA NA ...
  .. ..$ reversal_of_discount_window_t4                 : chr [1:100] NA NA NA NA ...
  .. ..$ interest_payment_issuance_efbn_t4              : chr [1:100] NA NA NA NA ...
  .. ..$ forecast_aggregate_bal_t4                      : int [1:100] NA NA NA NA NA NA NA NA NA NA ...
  .. ..$ forex_trans_u                                  : chr [1:100] "+0" "+0" "+0" "+0" ...
  .. ..$ other_market_activities_u                      : chr [1:100] "+0" "+0" "+0" "+0" ...
  .. ..$ reversal_of_discount_window_u                  : chr [1:100] "-0" "-0" "-0" "-0" ...
  .. ..$ interest_payment_issuance_efbn_u               : chr [1:100] "-107" "-104" "-75" "-75" ...
  .. ..$ forecast_aggregate_bal_u                       : int [1:100] 337479 337479 337479 337479 337479 337479 337479 337479 337479 337479 ...
interbank.liquidity$result
$datasize
[1] 100

$records
str(interbank.liquidity$result)
List of 2
 $ datasize: int 100
 $ records :'data.frame':   100 obs. of  44 variables:
  ..$ end_of_date                                    : chr [1:100] "2022-04-11" "2022-04-08" "2022-04-07" "2022-04-06" ...
  ..$ cu_weakside                                    : num [1:100] 7.85 7.85 7.85 7.85 7.85 7.85 7.85 7.85 7.85 7.85 ...
  ..$ cu_strongside                                  : num [1:100] 7.75 7.75 7.75 7.75 7.75 7.75 7.75 7.75 7.75 7.75 ...
  ..$ disc_win_base_rate                             : num [1:100] 0.75 0.75 0.75 0.75 0.75 0.75 0.75 0.75 0.75 0.75 ...
  ..$ hibor_overnight                                : num [1:100] 0.03 0.02 0.02 0.02 0.01 0.02 0.03 0.03 0.02 0.02 ...
  ..$ hibor_fixing_1m                                : num [1:100] 0.188 0.185 0.188 0.196 0.206 ...
  ..$ twi                                            : num [1:100] 96 95.8 95.6 95.7 95.6 95.5 95.2 95.3 95.7 95.9 ...
  ..$ opening_balance                                : int [1:100] 337554 337554 337554 337551 337551 337551 337551 337534 337534 337534 ...
  ..$ closing_balance                                : int [1:100] 337554 337554 337554 337554 337551 337551 337551 337551 337534 337534 ...
  ..$ market_activities                              : chr [1:100] "+0" "+0" "+0" "+0" ...
  ..$ interest_payment                               : chr [1:100] "+0" "+0" "+0" "+3" ...
  ..$ discount_window_reversal                       : chr [1:100] "-0" "-0" "-0" "-0" ...
  ..$ discount_window_activities                     : chr [1:100] "+0" "+0" "+0" "+0" ...
  ..$ intraday_movements_of_aggregate_balance_at_0930: int [1:100] 371125 375503 377226 375387 381938 371916 363729 363880 350696 357104 ...
  ..$ intraday_movements_of_aggregate_balance_at_1000: int [1:100] 375339 379266 385469 381909 386912 373675 367985 366723 358279 358428 ...
  ..$ intraday_movements_of_aggregate_balance_at_1100: int [1:100] 398461 398028 395229 406391 391314 395677 388202 383598 376635 375870 ...
  ..$ intraday_movements_of_aggregate_balance_at_1200: int [1:100] 404220 404131 403552 353051 404440 399350 402104 331056 383952 385132 ...
  ..$ intraday_movements_of_aggregate_balance_at_1500: int [1:100] 402494 400980 401072 348658 405804 398916 400683 334817 391180 389282 ...
  ..$ intraday_movements_of_aggregate_balance_at_1600: int [1:100] 403079 400951 402067 353770 408927 399310 404832 337197 392468 391754 ...
  ..$ forex_trans_t1                                 : chr [1:100] "+0" "+0" "+0" "+0" ...
  ..$ other_market_activities_t1                     : chr [1:100] "+0" "+0" "+0" "+0" ...
  ..$ reversal_of_discount_window_t1                 : chr [1:100] "-0" "-0" "-0" "-0" ...
  ..$ interest_payment_issuance_efbn_t1              : chr [1:100] "+0" "+0" "+0" "+0" ...
  ..$ forecast_aggregate_bal_t1                      : int [1:100] 337554 337554 337554 337554 337554 337551 337551 337551 337551 337534 ...
  ..$ forex_trans_t2                                 : chr [1:100] "+0" "+0" "+0" "+0" ...
  ..$ other_market_activities_t2                     : chr [1:100] "+0" "+0" "+0" "+0" ...
  ..$ reversal_of_discount_window_t2                 : chr [1:100] "-0" "-0" "-0" "-0" ...
  ..$ interest_payment_issuance_efbn_t2              : chr [1:100] "+32" "+0" "+0" "+0" ...
  ..$ forecast_aggregate_bal_t2                      : int [1:100] 337586 337554 337554 337554 337554 337534 337551 337551 337551 337541 ...
  ..$ forex_trans_t3                                 : chr [1:100] "+0" "+0" "+0" "+0" ...
  ..$ other_market_activities_t3                     : chr [1:100] "+0" "+0" "+0" "+0" ...
  ..$ reversal_of_discount_window_t3                 : chr [1:100] "-0" "-0" "-0" "-0" ...
  ..$ interest_payment_issuance_efbn_t3              : chr [1:100] "+0" "+29" "+0" "+0" ...
  ..$ forecast_aggregate_bal_t3                      : int [1:100] 337586 337583 337554 337554 337554 337534 337534 337551 337551 337541 ...
  ..$ forex_trans_t4                                 : chr [1:100] NA NA NA NA ...
  ..$ other_market_activities_t4                     : chr [1:100] NA NA NA NA ...
  ..$ reversal_of_discount_window_t4                 : chr [1:100] NA NA NA NA ...
  ..$ interest_payment_issuance_efbn_t4              : chr [1:100] NA NA NA NA ...
  ..$ forecast_aggregate_bal_t4                      : int [1:100] NA NA NA NA NA NA NA NA NA NA ...
  ..$ forex_trans_u                                  : chr [1:100] "+0" "+0" "+0" "+0" ...
  ..$ other_market_activities_u                      : chr [1:100] "+0" "+0" "+0" "+0" ...
  ..$ reversal_of_discount_window_u                  : chr [1:100] "-0" "-0" "-0" "-0" ...
  ..$ interest_payment_issuance_efbn_u               : chr [1:100] "-107" "-104" "-75" "-75" ...
  ..$ forecast_aggregate_bal_u                       : int [1:100] 337479 337479 337479 337479 337479 337479 337479 337479 337479 337479 ...
interbank.records = interbank.liquidity$result$records %>% as_tibble()
interbank.records

interbank.records %>% 
ggplot() +
  geom_line(
    mapping=aes(x=end_of_date, y=hibor_fixing_1m, group=1),
    color="orange"
             )


GROUPING AND AGGREGATION

Using group_by() and summarise()

graduates %>% group_by(AcademicYear, LevelOfStudy) %>% 
  summarise(TotalHeadcount = sum(Headcount)) 
`summarise()` has grouped output by 'AcademicYear'. You can override using the
`.groups` argument.
graduates %>% group_by(AcademicYear, LevelOfStudy) %>% 
  summarise(TotalHeadcount = sum(Headcount)) %>% 
  ggplot(
    aes(x=AcademicYear, 
             y=TotalHeadcount,
             group=LevelOfStudy, 
             color=LevelOfStudy
             )
    ) +
    geom_line() +
    geom_point() 
`summarise()` has grouped output by 'AcademicYear'. You can override using the
`.groups` argument.

NA

Use of filter()

Use filter() to keep only “Taught Postgraduate” Records

This plot is not very useful without previously applying filter() and group_by() and summarise()

graduates %>% 
  filter(LevelOfStudy=="Taught Postgraduate") %>% 
  ggplot() +
    geom_line(mapping=aes(x=AcademicYear,y=Headcount, group=ProgrammeCategory, color=ProgrammeCategory))

filter() + group_by() + summarise()

Use filter() to extract required rows Use group_by() and summarise() to group and aggregate total headcount for both male and female


graduates %>% 
  filter(LevelOfStudy=="Taught Postgraduate") %>% 
  group_by(AcademicYear, ProgrammeCategory) %>% 
  summarise(TotalHeadcount = sum(Headcount)) %>% 
  ggplot() +
    geom_line(mapping=aes(x=AcademicYear,y=TotalHeadcount, group=ProgrammeCategory, color=ProgrammeCategory))
`summarise()` has grouped output by 'AcademicYear'. You can override using the
`.groups` argument.

# Following is the same chart for "undergraduate" 
# graduates %>%
#   filter(LevelOfStudy=="Undergraduate") %>%
#   group_by(AcademicYear, ProgrammeCategory) %>%
#   summarise(TotalHeadcount = sum(Headcount)) %>%
#   ggplot() +
#     geom_line(mapping=aes(x=AcademicYear,y=TotalHeadcount, group=ProgrammeCategory, color=ProgrammeCategory))
    

geom_col() function

More Aggregation Functions

Center: mean(), median() Spread: sd(), IQR(), mad() Range: min(), max(), quantile() Position: first(), last(), nth(), Count: n(), n_distinct() Logical: any(), all()

More information at summarise() function

geom_bar() function

bar chart give the counting frequency (number of record in the data set)

graduates %>% 
ggplot() +
  geom_bar(mapping=aes(x=AcademicYear)) # you only need to provide the x axis

box plot

The boxplot compactly displays the distribution of a continuous variable. It visualises five summary statistics (the median, two hinges and two whiskers), and all “outlying” points individually.

graduates %>% 
ggplot() +
  geom_point(mapping=aes(x=Sex, y=Headcount))


graduates %>% 
ggplot() +
  geom_boxplot(mapping=aes(x=LevelOfStudy, y=Headcount))

facet_wrap()

facets are useful categorical variables. It split your plot into subplot (a.k.a facets) that each display one subset of the data.

facet_wrap() lets you work with ONE extra variable (besides x and y).
Each categorical value will used to produce to a sub plot. We use ‘LevelOfStudy’ here. Since there are FOUR distinct values, you will see FOUR sub-plots

graduates %>% ggplot() +
  geom_point(mapping = aes(x=AcademicYear, y=Headcount)) +
  facet_wrap(~ LevelOfStudy) # You will see FOUR sub plots as there are FOUR distinct values for this variable.

facet_grid()

facet_grid() lets you work with TWO extra variables (besides x and y).
Each combination of these two variables’ value are used to produce to a sub plot.

You should SEE 28 sub plots as there are FOUR distinct values for ‘LevelOfStudy’ and 7 distinct values for ‘ProgrammeCategory’.


MAKE IT PRETTY

Use of title, label, background color and themes

# in this example we save the plot to a variable name 'level.bar.plot' so that we can use it again and again.
level.bar.plot = graduates %>% 
filter(ProgrammeCategory=="Engineering and Technology") %>% 
ggplot() +
  geom_col(mapping=aes(x=AcademicYear, y=Headcount, fill=LevelOfStudy))

# To show the plot, just use print() function with the previous saved plot variable as parameter.
print(level.bar.plot)

Plot Background

element_rect() is a function to generated rectangle geometry element. You have to specify the fill parameter by color name or hex code code by string

Plot Background refers to the big area of everything relevant to the plot.

level.bar.plot # default style


level.bar.plot +
  theme(plot.background = element_rect(fill="orange")) # styling the plot background

Panel Background

Panel background refers to the inner area of plot. Area for showing header, axis label and legend are NOT included.

level.bar.plot # default style


level.bar.plot +
  theme(panel.background = element_rect(fill="orange")) # styling the panel background

Remove Plot and Panel Background

In visual design, color is a very powerful tool to guide users’ attention. But you have to use them carefully.

Too many colors will usually do the opposite - confuse the audience. Minimal design is the recent trend. Especially true when many are using small device like mobile phone or tablet for day-to-day communication.

In this example, we are removing both plot and panel background to achieve a clean design. After all, background is NOT the main dish. Very often background color causes distraction to graph.

level.bar.plot # default style


level.bar.plot +
  theme(panel.background = element_blank()) + # styling the panel background to none
  theme(plot.background = element_blank()) + # styling the plot background to none
  theme(panel.grid.major.y = element_line(color="grey")) # styling the grid line for y-axis

Change Label for x/y Axis

level.bar.plot # default style


level.bar.plot +
  theme(panel.background = element_blank()) + # styling the panel background to none
  theme(plot.background = element_blank()) + # styling the plot background to none
  theme(panel.grid.major.y = element_line(color="grey")) + # styling the grid line for y-axis
  ylab("Number of Student") + # Label for Y axis
  xlab("Year") # Label for X axis

Ratate the Labe Text

level.bar.plot # default style


level.bar.plot +
  theme(panel.background = element_blank()) + # styling the panel background to none
  theme(plot.background = element_blank()) + # styling the plot background to none
  theme(panel.grid.major.y = element_line(color="grey")) + # styling the grid line for y-axis
  ylab("Number of Student") + # Label for Y axis
  xlab("Year") + # Label for X axis
  theme(axis.text.x = element_text(angle = 45))

Change Fill Colors

level.bar.plot # default style


level.bar.plot +
  theme(panel.background = element_blank()) + # styling the panel background to none
  theme(plot.background = element_blank()) + # styling the plot background to none
  theme(panel.grid.major.y = element_line(color="grey")) + # styling the grid line for y-axis
  ylab("Number of Student") + # Label for Y axis
  xlab("Year") + # Label for X axis
  scale_fill_manual(values=c("purple", "orange", "blue", "tomato")) # use c() function to specify color list

Styling The Legends

level.bar.plot # default style


level.bar.plot +
  theme(panel.background = element_blank()) + # styling the panel background to none
  theme(plot.background = element_blank()) + # styling the plot background to none
  theme(panel.grid.major.y = element_line(color="grey")) + # styling the grid line for y-axis
  ylab("Number of Student") + # Label for Y axis
  xlab("Year") + # Label for X axis
  theme(legend.position="top") +
  scale_fill_manual(values=c("purple", "orange", "blue", "tomato"),
                    guide = guide_legend(title="Level of Study", 
                                         label.position = "bottom")
                    ) # move legend position to top and label position to bottom 

NA

Add Title and Subtitle

level.bar.plot # default style


level.bar.plot +
  theme(panel.background = element_blank()) + # styling the panel background to none
  theme(plot.background = element_blank()) + # styling the plot background to none
  theme(panel.grid.major.y = element_line(color="grey")) + # styling the grid line for y-axis
  ylab("Number of Student") + # Label for Y axis
  xlab("Year") + # Label for X axis
  theme(legend.position="top") +
  scale_fill_manual(values=c("purple", "orange", "blue", "tomato"),
                    guide = guide_legend(title="Level of Study", 
                                         label.position = "bottom")
                    ) + # move legend position to top and label position to bottom 
  ggtitle("Hong Kong Higher Education Student Headcount", subtitle="2009 - 2019")

Add Annotations Texts

Add extra texts/shape to enhance your visualization

level.bar.plot # default style


level.bar.plot +
  theme(panel.background = element_blank()) + # styling the panel background to none
  theme(plot.background = element_blank()) + # styling the plot background to none
  theme(panel.grid.major.y = element_line(color="grey")) + # styling the grid line for y-axis
  ylab("Number of Student") + # Label for Y axis
  xlab("Year") + # Label for X axis
  theme(legend.position="top") +
  scale_fill_manual(values=c("purple", "orange", "blue", "tomato"),
                    guide = guide_legend(title="Level of Study", 
                                         label.position = "bottom")
                    ) + # move legend position to top and label position to bottom 
  ggtitle("Hong Kong Higher Education Student Headcount", subtitle="2009 - 2020") + 
  annotate("text", label="Record\nHigh", x="2017/18", y=5300) # you can change text position value of x and y to set the text position

Adding Reference Lines

level.bar.plot # default style


level.bar.plot +
  theme(panel.background = element_blank()) + # styling the panel background to none
  theme(plot.background = element_blank()) + # styling the plot background to none
  theme(panel.grid.major.y = element_line(color="grey")) + # styling the grid line for y-axis
  ylab("Number of Student") + # Label for Y axis
  xlab("Year") + # Label for X axis
  theme(legend.position="top") +
  scale_fill_manual(values=c("purple", "orange", "blue", "tomato"),
                    guide = guide_legend(title="Level of Study", 
                                         label.position = "bottom")
                    ) + # move legend position to top and label position to bottom 
  ggtitle("Hong Kong Higher Education Student Headcount", subtitle="2009 - 2019") + 
  annotate("text", label="Record\nHigh", x="2017/18", y=5300) + # you can change text position value of x and y to set the text position
  geom_hline(yintercept=3200) + # adds horizontal line
  geom_vline(xintercept = "2017/18", color="white") # adds vertical line

NA

Using Themes

More 3rd-party Themes

Install ggthemes package to unlock wider selections of themes.


MORE RESOURCES ON ggplot2

official website

browseURL("https://ggplot2.tidyverse.org/")

MODELS

Work Follow of Data Science

Data Science is combination of efforts and results of programming, mathematics and domain expertise. Among all, mathematics is the foundation of models. With models, data scientists make predictions; discover hidden patterns; and conclude insights.

Modeling is usually an iterative process among data transformation, data visualization, exploring with models and fitting.

What exactly is a Model?

Human are good in drawing conclusions and providing insight while are NOT good in directly facing large number of data attributes and huge volume of raw data.

Models Example

A model is mathematics expression that provides a simple low-dimensional summary of a data set so that we can draw conclusion and even provide insights.

Models only provide approximation (NOT the exact truth).

Basic Concepts of Model

Let’s do some simple R coding to uncover the basic concept of model

if (!require("pacman")) install.packages("pacman") # install pacman
pacman::p_load(pacman, tidyverse, modelr, magrittr) # install (or load) required packages

Let’s use a simple built-in data set sim1 for exploring. In this simulation data, you can strongly see the pattern with the help of simple scatter-plot.

p_data(modelr) # display all the built-in data sets of modelr
  Data    Description              
1 heights Height and income data.  
2 sim1    Simple simulated datasets
3 sim2    Simple simulated datasets
4 sim3    Simple simulated datasets
5 sim4    Simple simulated datasets
print(heights)
?heights
print(sim1) # contains two continuous variables: x, y

ggplot(sim1, aes(x, y)) +
  geom_point()

Generating a Random Linear Model

Linear line and quadratic curve are widely used to explore the relation of two variables. Let’s take linear model as simple example to grab the essence of model.

A linear model is described as y = a1 + x * a2 x and y are the variable from data set a1 and a2 are parameters that can vary to capture different patterns.

Let’s generate a random value of a1 as intercept and a2 as slope. Here, we use runinf() to generate random uniform distributed number

Note: You might have to repeatively run a few times before you can see the visualized random model represented by a orange straight line.

model = tibble(
  a1 = runif(1, -20, 40), # random intercept value between -20 to 40
  a2 = runif(1, -5, 5) # random slop value between -5 to 5
)
# print(model) # un-comment to print the random model

ggplot(sim1, aes(x,y)) +
  geom_point() + # this plots all the data
  geom_abline(aes(intercept = a1, slope = a2), 
              data=model, 
              color="Orange"
              ) # this add the straight line, a.k.a, our random model on top to data layer

Generating 250 Random Models as Candidate Models

The number of potential models are unlimited. Let’s try to generate 250 random ones as candidate models.
Among these 250 models, some are very bad that you can judge even by glancing. Some are not bad but we don’t know which one is the best among them.

models = tibble(
  a1 = runif(250, -20, 40), # 250 random intercept values between -20 to 40
  a2 = runif(250, -5, 5) # 250 random slop values between -5 to 5
)

# let add these random linear models as overlay on top of data 
ggplot(sim1, aes(x,y)) +
  geom_point() +
  geom_abline(aes(intercept = a1, slope = a2), data=models, alpha=0.2)

Selecting the Most Fitting Ten Models

# this function calculates the modeled y value of each given 'x' value
modeled_y = function(a, data) {
  a[1] + data$x * a[2] # a[1] is the intercept and a[2] is the slope
}
# modeled_y(c(7, 1.5), sim1) # test-run modeled_y function

# this function calculates the distance between an actual y value and the predicted y value (modeled_y)
measure_distance = function(mod, data) {
  diff <- data$y - modeled_y(mod, data) # mod is random intercept and slope of a certain model
  sqrt(mean(diff ^ 2)) # root-mean-squared deviation to compute overall distance
}
# measure_distance(c(7, 1,5), sim1) # test-run measure_distance function

# this function calculates the 'overall' distance for a given model with a1 as intercept and a2 as slope
sim1_dist = function(a1, a2) {
  measure_distance(c(a1, a2), sim1) # a1 is the intercept of a model while a2 is the slope
}

# use map2_dbl (a mapping function) to a new column named 'dist' to each random model
models %<>% 
  mutate(dist = purrr::map2_dbl(a1, a2, sim1_dist))
models # now we have an extra column named 'dist' in our models

# plotting the best 10 models from the ranked distances
ggplot(sim1, aes(x, y)) + 
  geom_point(size = 2, colour = "grey30") + 
  geom_abline(
    aes(intercept = a1, slope = a2, color = -dist) , 
    data = filter(models, rank(dist) <= 10) # To show only the best 5, change 10 to five
  )

Using lm() function

If the previous R codes on choosing best 10 among 250 random models are too much digest. It’s fine. It’s just for you to feel the process and essence of models and fitting models.

In fact, R makes linear model fitting extremely easy by just one single line of function calling to the lm() function (a built-in linear model fitting function)

lm() finds the closest model in a single step, using a sophisticated algorithm that involves geometry, calculus, and linear algebra.

lm() has a special way to specify the model family: formulas. Formulas look like y ~ x, which lm() will translate to a function like y = a1 + a2 * x

# regular function calling manner
sim1_auto_model = lm(y ~ x, data = sim1) # finding the optimized linear model
# Or using piping below
sim1_auto_model = sim1 %>%  lm(y ~ x, .) # since piping always assumes outputs from previous function call will be the first parameter of next function, here we use "." to indicate sim1 will be fed as second parameter

print(sim1_auto_model) # print out the auto generated linear model

Call:
lm(formula = y ~ x, data = .)

Coefficients:
(Intercept)            x  
      4.221        2.052  
print(summary(sim1_auto_model)) # print out the summary of the generated linear model

Call:
lm(formula = y ~ x, data = .)

Residuals:
    Min      1Q  Median      3Q     Max 
-4.1469 -1.5197  0.1331  1.4670  4.6516 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   4.2208     0.8688   4.858 4.09e-05 ***
x             2.0515     0.1400  14.651 1.17e-14 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2.203 on 28 degrees of freedom
Multiple R-squared:  0.8846,    Adjusted R-squared:  0.8805 
F-statistic: 214.7 on 1 and 28 DF,  p-value: 1.173e-14
sim1_coef = coef(sim1_auto_model) # retrieves model's intercept and slope
print(sim1_coef)
(Intercept)           x 
   4.220822    2.051533 
# visualize the auto generated linear model on top of the sim1 data
ggplot(sim1, aes(x, y)) + 
  geom_point(size = 2, colour = "grey30") + 
  geom_abline(
    aes(intercept = sim1_coef[1], slope = sim1_coef[2]) 
  )

Making Prediction

new.data = data.frame(x = c(1,2,3,4,5,6,7,8,9,10))
predict(sim1_auto_model, new.data)
        1         2         3         4         5         6         7         8 
 6.272355  8.323888 10.375421 12.426954 14.478487 16.530020 18.581553 20.633087 
        9        10 
22.684620 24.736153 

Using lm() function with categorical data

Using lm() function on a categorical variable will use mean value for each category for prediction.

p_data(modelr) # shows included data sets with modelr package
  Data    Description              
1 heights Height and income data.  
2 sim1    Simple simulated datasets
3 sim2    Simple simulated datasets
4 sim3    Simple simulated datasets
5 sim4    Simple simulated datasets
sim2 %>% summary()
      x                   y          
 Length:40          Min.   :-0.9101  
 Class :character   1st Qu.: 1.3121  
 Mode  :character   Median : 3.4199  
                    Mean   : 4.3266  
                    3rd Qu.: 6.9868  
                    Max.   :10.7554  
sim2 
model_cat = sim2 %>%  lm(y~x, .)
print(model_cat)

Call:
lm(formula = y ~ x, data = .)

Coefficients:
(Intercept)           xb           xc           xd  
     1.1522       6.9639       4.9750       0.7588  
grid = sim2 %>% 
  data_grid(x) %>% 
  add_predictions(model_cat)
grid

ggplot(sim2, aes(x)) + 
  geom_point(aes(y = y)) +
  geom_point(data = grid, aes(y = pred), colour = "red", size = 4)

sim3

Multiple Regression

Multiple regression is an extension of linear regression into relationship between more than two variables.

?mtcars
print(mtcars)
m.r.model = mtcars %>% lm(mpg~disp+hp+wt, .)
print(m.r.model)

Call:
lm(formula = mpg ~ disp + hp + wt, data = .)

Coefficients:
(Intercept)         disp           hp           wt  
  37.105505    -0.000937    -0.031157    -3.800891  
new.data.2 = data.frame(
  disp = c(221),
  hp = c(102),
  wt = c(2.91)
  ) %>% print()

predict(m.r.model, new.data.2)
       1 
22.65987 

Logistic Regression

predict(bi.model, new.data.3)
        1 
-10.79681 

VISUALIZATION VS. MODEL

Visualization is usually thought as a tool for hypothesis generation to explore the hidden patterns among data while modelling is usually thought as at tool for hypothesis confirmation (to confirm what are found by data visualization tools). These two tools are suggested to used in an iterative manner so as to achieve a deeper revealing on data.

FURTHER LEARNING RESOURCES

More on ggplot2

ggplot2 tutorial

browseURL("https://www.tutorialspoint.com/ggplot2/ggplot2_introduction.htm")

ggplot2: Elegant Graphics for Data Analysis (Web-based E-book) Elegant Graphics for Data Analysis ###

browseURL("https://ggplot2-book.org/")

R Statiscal Models

more on model building

A execellent ebook for R Data Science * Basic Concept of Model * Model Building * Example on Model A web based ebook at R for Data Science

browseURL("https://r4ds.had.co.nz/model-intro.html")

examples on more models

  • Linear Regression
  • Multiple Regression
  • Logistic Regression
  • Decision Tree
  • Random Forest
  • And so on
browseURL("https://www.tutorialspoint.com/r/r_linear_regression.htm")

R Studio Cheatsheets

Many quick syntax reference for R programming and 3-rd packages R Studio Cheatsheets

browseURL("https://www.rstudio.com/resources/cheatsheets/")

Advanced R

Advanced R

browseURL("https://adv-r.hadley.nz/index.html")
LS0tCnRpdGxlOiAiUiBJbnRlcm1lZGlhdGUgLSBEYXkgMiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKIyBSIERBVEEgVklTVUFMSVpBVElPTgoKIyMgV2h5IERhdGEgVmlzdWFsaXphdGlvbj8KUmF3IGRhdGEgcG9pbnRzIGRvbid0IHByb3ZpZGUgbXVjaCBpbnNpZ2h0IHRvIGtpY2sgb2ZmIGRhdGEgYW5hbHlzaXMuCgpEYXRhIFZpc3VhbGl6YXRpb24gaXMgYnJpbGxpYW50IGluCiogZXhwbG9yaW5nIHRoZSBwYXR0ZXJuIG9mIGRhdGEgYnJpZWZseSBhdCB0aGUgZWFybHkgc3RhZ2UKKiBpbiB0aGUgZmluYWwgY29uY2x1c2lvbiwgZW5oYW5jZSB0aGUgc3RvcnkgdGVsbGluZyBvZiBkYXRhIGFuYWx5c2lzIAoKCiMjIFJlbWluZGVyIG9uIFdvcmtmbG93IEFnYWluCgohW1IgRGF0YSBTY2llbmNlIFdvcmtmbG93XShodHRwczovL2QzM3d1YnJma2kwbDY4LmNsb3VkZnJvbnQubmV0LzU3MWIwNTY3NTdkNjhlNmRmODFhM2UzODUzZjU0ZDNjNzZhZDZlZmMvMzJkMzcvZGlhZ3JhbXMvZGF0YS1zY2llbmNlLnBuZykKTGlrZSB0aGUgcGljdHVyZSBhYm92ZSBzaG93cywgaW4gdGhlIHN0YWdlIG9mICoqVW5kZXJzdGFuZCoqIChleHBsb3JpbmcgdGhlIGRhdGEgc2V0cyksIFRyYW5zZm9ybSwgVmlzdWFsaXplIGFuZCBNb2RlbCBhcmUgdXNlZCBpbiBhbiBpdGVyYXRpdmUgbWFubmVyIHNvIGFzIHRvIGdldCB0aGUgYmVzdCBlYXJseSB1bmRlcnN0YW5pbmcgYWJvdXQgdGhlIGRhdGEgc2V0cy4KCgojIyBCdWlsdC1pbiBQbG90IEZ1bmN0aW9ucwpUaGUgYWR2YW50YWdlIG9mIHVzaW5nIGJ1aWx0LWluIHBsb3R0aW5nIHV0aWxpdGllcyBpcyB0aGV5IGFyZSBlYXN5LgoKSXQgbGV0IHlvdSBxdWlja2x5IHZpc3VhbGl6ZSB0aGUgZGF0YSBwYXR0ZXJuIHdoaWxlIHlvdSBhcmUgdHJ5aW5nIHRvIGdhaW4gYSBicmllZiBpbnNpZ2h0IGF0IHRoZSBlYXJseSBzdGFnZSBvZiB5b3VyIHdvcmtmbG93LgpgYGB7cn0KcGxvdChpcmlzKQpgYGAKCiMjIFJlZnJlc2ggb24gQnVpbHQtaW4gUGxvdCBUb29scwpGb3IgYnVpbHQtaW4gUiBkYXRhIHZpc3VhbGl6YXRpb24sIGdvIHRvIHRoZSAqKlIgSW50cm8qKiBwcm9qZWN0IG9uIEdpdGh1YiB0byByZWZyZXNoIHlvdXIgbWVtb3J5CihSIEludHJvIFNvdXJjZSBDb2RlcylbaHR0cHM6Ly9naXRodWIuY29tL25nc2FubHVrL1ItSW50cm9dCgoKIyMgR3JhbW1hciBvZiBHcmFwaGljczogZ2dwbG90MgoKSWYgdGhlIGJ1aWx0LWluIHBsb3R0aW5nIHRvb2xzIGFyZSBub3QgZW5vdWdoIGZvciB5b3UsIApnbyBmb3IgKipnZ3Bsb3QyKiouIApJdCAgaXMgdGhlIG1vc3QgcG9wdWxhciBkYXRhIHZpc3VhbGl6YXRpb24gZm9yIFIuCgpnZ3Bsb3QyIGlzIGFuIG9wZW4tc291cmNlIGRhdGEgdmlzdWFsaXphdGlvbiBwYWNrYWdlIGZvciBSLiAKZ2dwbG90MiBicmVha3MgdXAgZ3JhcGhzIGludG8gc2VtYW50aWMgY29tcG9uZW50cyBzdWNoIGFzICoqc2NhbGVzKiogYW5kICoqbGF5ZXJzKiouClNpbmNlIDIwMDUsIGdncGxvdDIgaGFzIGdyb3duIGluIHVzZSB0byBiZWNvbWUgb25lIG9mIHRoZSBtb3N0IHBvcHVsYXIgUiBwYWNrYWdlcy4KCgojIyBnZ3Bsb3QyIENoZWF0IFNoZWV0CgpbZ2dwbG90IDIgY2hlYXQgc2hlZXRdKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL2Jsb2IvbWFpbi9kYXRhLXZpc3VhbGl6YXRpb24tMi4xLnBkZikKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyBJTlNUQUxMIC8gTE9BRCBQQUNLQUdFUwpgYGB7cn0KaWYgKCFyZXF1aXJlKCJwYWNtYW4iKSkgaW5zdGFsbC5wYWNrYWdlcygicGFjbWFuIikgIyBjaGVjayBpZiBwYWNtYW4gYWxyZWFkeSBpbnN0YWxsZWQuIElmIG5vdCwgaW5zdGFsbCBpdC4KcGFjbWFuOjpwX2xvYWQoCiAgcGFjbWFuLCAjIHBhY2thZ2UgbWFuYWdlcgogIGRhdGFzZXRzLCAjIGJ1aWx0LWluIGRhdGEgc2V0cwogIHJpbywgIyByIGlucHV0IC8gb3V0cHV0CiAgbWFncml0dHIsICMgZm9yIHBpcGluZyBjb21tYW5kcwogIHRpZHl2ZXJzZSwgCiAgbW9kZWxyICMgbWF0aGVtYXRpY3MgbW9kZWwKKSAjIExvYWQgcmVxdWlyZWQgcGFja2FnZXMuICBJZiB0aGV5IGFyZSBOT1QgYWxyZWFkeSBkb3dubG9hZGVkLCBkb3dubG9hZCB0aGVtIGF1dG9tYXRpY2FsbHkuCmBgYAoKIyBCQVNJQyBHUkFNTUFSCmdncGxvdDIgaXMgYmFzZWQgb24gdGhlIGdyYW1tYXIgb2YgZ3JhcGhpY3MsIHRoZSBpZGVhIHRoYXQgeW91IGNhbiBidWlsZCBldmVyeSBncmFwaCBmcm9tIHRoZSBzYW1lIGNvbXBvbmVudHMgYmVsb3c6IAoKKiphIGRhdGEgc2V0KiogCisgCioqYSBjb29yZGluYXRlIHN5c3RlbSoqIAorIAoqKmFuZCBnZW9t4oCUdmlzdWFsIHRoYXQgcmVwcmVzZW50IGRhdGEgcG9pbnRzKioKCiFbR3JhbW1hciBvZiBHcmFwaGljc10oaHR0cHM6Ly9qdWxlczMyLmdpdGh1Yi5pby9yLWZvci1leGNlbC11c2Vycy9pbWcvcnN0dWRpby1jaGVhdHNoZWV0LWdncGxvdC5wbmcpCgoKIyMgVXNlIEJ1aWx0LWluIERhdGEgU2V0cwpMZXQncyB1c2UgdGhlIGJ1aWx0LWluIGNhcnMgZGF0YSBzZXQgZm9yIGEgc2ltcGxlIGdncGxvdAoKYGBge3J9CnByaW50KGNhcnMpCmBgYAoKYGBge3IgR2VuZXJhdGluZyBFbXB0eSBQbG90fQpjYXJzICU+JSBnZ3Bsb3QoKSAKIyBUaGlzIG9ubHkgc3BlY2lmaWVzIGEgZGF0YSBzZXQgYW5kIGEgY29vcmRpbmF0ZSBzeXN0ZW0gCiMgYW5kIHRoZXJlZm9yZSBpdCdzIGFuIGVtcHR5IHBsb3QKYGBgCgojIyBnZW9tX3BvaW50KCkgZnVuY3Rpb24KSXQncyBlYXN5IHRvIGFkZCBnZW9tZXRyeSBsYXllciB0byB0aGUgYmFzZSBjby1vcmRpbmF0ZQpMZXQncyBBREQgYSBsYXllciBvZiBkYXRhIHBvaW50cyB1c2luZyAqKmdlb21fcG9pbnQoKSoqIGZ1bmN0aW9uLgoKQW5kIFllcywgeW91IGNhbiBBREQgYSBsYXllciBieSB1c2luZyB0aGUgKyBvcGVyYXRvcgpXZSB1c2UgKipnZW9tX3BvaW50KCkqKiBmdW5jdGlvbiB0aGF0IHJlcXVpcmVzIHggYW5kIHkgdmFsdWUgZm9yIGVhY2ggZ2VvbSBwb2ludC4KSW4gMkQgY28tb3JkaW5hdGUsIGEgcG9pbnQgaXMgZGVzY3JpYmVkIGJ5IGl0cyB4IGFuZCB5IHZhbHVlLgoKV2UgbmVlZCB0byBwcm92aWRlIGEgbWFwcGluZyB0aGF0IHNwZWNpZmllcyB0aGUgZGF0YSBjb2x1bW5zJyBuYW1lIHRvIG1hcCB0byBhIHBvaW50J3MgeCBhbmQgeSB2YWx1ZQoKVGhhdCBtYXBwaW5nIGlzIGRlZmluZWQgYnkgYW4gYWVzdGhldGljcyBmdW5jdGlvbjogCioqYWVzKCkqKgoKV2UgdXN1YWxseSBuYW1lIHRoZSBwbG90IGJ5IHRoZSBuYW1lIG9mIGdlb20gdGhhdCB3ZSB1c2UgdG8gcmVwcmVzZW50IGRhdGEuCkluIHRoaXMgY2FzZSwgaXQncyB3aWRlbHkgY2FsbGVkIFNjYXR0ZXIgUGxvdC4KSXQgaXMgdXNlZnVsIHRvIGV4cGxvcmUgdGhlIHJlbGF0aW9uIG9mIHR3byB2YXJpYWJsZXMuCgpgYGB7cn0KY2FycyAlPiUgZ2dwbG90KCkgKyAjIE5PVEU6ICcrJyBvcGVyYXRvciBtdXN0IGJlIHBsYWNlZCBhdCB0aGUgZW5kIG9mIGEgbGluZQogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4PXNwZWVkLCB5PWRpc3QpKQpgYGAKCiMjIFVzZSBnZW9tX2xpbmUoKSB0byByZXBsYWNlIGdlb21fcG9pbnQoKQpnZW9tX3BvaW50KCkgYW5kIGdlb21fbGluZSgpIHJlcXVpcmUgdmVyeSBzaW1pbGFyIHBhcmFtZXRlcnMuCmdlb21fbGluZSgpIGlzIHNpbXBseSBhbiBlbmhhbmNlZCB2aXN1YWxpemF0aW9uIHRoYXQgYXV0b21hdGljYWxseSBjb25uZWN0IGFsbCB0aGUgcG9pbnRzLgpMaW5lIGNoYXJ0IGFyZSB1c2VkIHRvIGV4cGxvcmUvZW1waGFzaXplIHRyZW5kaW5nLgoKYGBge3J9CiMganVzdCBjaGFuZ2UgZ2VvbV9wb2ludCB0byBnZW9tX2xpbmUgd2l0aG91dCBjaGFuZ2UgYW55dGhpbmcgZWxzZQpjYXJzICU+JSBnZ3Bsb3QoKSArCiAgZ2VvbV9saW5lKG1hcHBpbmcgPSBhZXMoeD1zcGVlZCwgeT1kaXN0KSkgCmBgYAoKCiMjIFVzZSBnZW9tX3Ntb3RoKCkgdG8gcHJvamVjdCBhIHNtb290aCBsaW5lCmBnZW9tX3Ntb290aCgpYCBhbmQgYGdlb21fcG9pbnQoKWAgcmVxdWlyZSB2ZXJ5IHNpbWlsYXIgcGFyYW1ldGVycy4KYGdlb21fc21vb3RoKClgIHNtb290aHMgb3V0IHRoZSBsaW5lIHByb2dyZXNzaW9uCgpgYGB7cn0KIyBqdXN0IGNoYW5nZSBnZW9tX3BvaW50IHRvIGdlb21fbGluZSB3aXRob3V0IGNoYW5nZSBhbnl0aGluZyBlbHNlCmNhcnMgJT4lIGdncGxvdCgpICsKICBnZW9tX3Ntb290aChtYXBwaW5nID0gYWVzKHg9c3BlZWQsIHk9ZGlzdCkpIApgYGAKCiMjIEdlb21ldHJpYyBPYmplY3RzIENvbXBhcmlzb24KYSBnZW9tIGlzIHRoZSBnZW9tZXRyaWNhbCBvYmplY3QgdGhhdCBhIHBsb3QgdXNlIHRvIHJlcHJlc2VudCBkYXRhLgpXZSB1c2VkIHBvaW50cywgbGluZXMgYW5kIHNtb290aCBhYm92ZSBvbiB0aGUgc2FtZSBkYXRhIHNldCBhbmQgdGhleSBwcm92aWRlIHZlcnkgZGlmZmVyZW50IG1lc3NhZ2UuIAoKV2UgdXN1YWxseSBuYW1lIHRoZSBwbG90IGJ5IHRoZSBuYW1lIG9mIGdlb20gdGhhdCB3ZSB1c2UgdG8gcmVwcmVzZW50IGRhdGEuCgojIyBBZGRpbmcgTXVsdGlwbGUgTGF5ZXJzCmBgYHtyfQojIGp1c3QgY2hhbmdlIGdlb21fcG9pbnQgdG8gZ2VvbV9saW5lIHdpdGhvdXQgY2hhbmdlIGFueXRoaW5nIGVsc2UKY2FycyAlPiUgZ2dwbG90KCkgKwogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4PXNwZWVkLCB5PWRpc3QpKSArCiAgZ2VvbV9zbW9vdGgobWFwcGluZyA9IGFlcyh4PXNwZWVkLCB5PWRpc3QpKSAKYGBgCgojIyBBZGRpbmcgQWVzdGhldGljcyB0byB5b3VyIFBsb3RzClVuLWNvbW1lbnQgdGhlIGV4dHJhIHBhcmFtZXRlcnMgdG8gYWRkIG1vcmUgYWVzdGhldGljcyB0byB5b3VyIHBsb3QKYGBge3J9CmNhcnMgJT4lIGdncGxvdCgpICsKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeD1zcGVlZCwgeT1kaXN0KSwKICAgICAgICAgICAgIGNvbG9yID0gIm9yYW5nZSIsICMgdGhlIGNvbG9yIG9mIGRhdGEgcG9pbnRzCiAgICAgICAgICAgICAjIHNpemUgPSAzLCAjIHRoZSBzaXplIG9mIGRhdGEgcG9pbnQKICAgICAgICAgICAgICMgYWxwaGEgPSAwLjUsICMgdGhlIHRyYW5zcGFyZW5jeSBvZiBkYXRhIHBvaW50cywgbWluPTAsIG1heD0xCiAgICAgICAgICAgICAjIHNoYXBlID0gMCwgIyB0aGUgc2hhcGUgb2YgZGF0YSBwb2ludAogICAgICAgICAgICAgKQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyBQTE9UIFdJVEggT1VSIE9XTiBEQVRBCgojIyBMb2FkaW5nIERhdGE6IGFsbG93YW5jZQpgYGB7ciByZWFkaW5nIGRhdGEgZmlsZXN9CmFsbG93YW5jZSA9IHJlYWRfY3N2KCIuL2RhdGEvYWxsb3dhbmNlLmNzdiIpCnByaW50KGFsbG93YW5jZSkKYGBgCgoKIyMgQWxsb3dhbmNlIERhdGEgU2V0IGluIFNpbXBsZSBTY2F0dGVycGxvdApgYGB7ciBBbGxvd2FuY2UgU2NhdHRlcnBsb3R9CmFsbG93YW5jZSAlPiUgCiAgZ2dwbG90KCkgKyAKICBnZW9tX3BvaW50KAogICAgbWFwcGluZz1hZXMoeD1Bc3Nlc3NtZW50X1llYXIsIHk9QmFzaWMpLAogICAgY29sb3IgPSAib3JhbmdlIiwKICAgIHNpemUgPSAzCiAgICApIApgYGAKCgojIyBDb250aW51b3VzIFZhbHVlcyB2cy4gRGlzY3JldGUgVmFsdWVzCkNvbnRpbnVvdXMgdmFsdWVzIHJlZmVyIHRvIG51bWJlcnMgdmFsdWUgdGhhdCBoYXMgd2lkZSByYW5nZS4gRS5nLiBzYWxhcnksIGhlaWdodC4KCkRpc2NyZXRlIHZhbHVlcyByZWZlciB0byBhIGxpbWl0ZWQgbnVtYmVyIG9mIHZhbGlkIHZhbHVlcy4gIEl0IGNhbiBiZSBzdHJpbmcuIEl0IGNhbiBiZSBhIGZldyBkaXN0aW5jdCBudW1iZXJzLgoKV2hlbiB5b3UgcHJvZHVjZSBwbG90cywgcGF5IGF0dGVudGlvbiB0byB3aGF0IHR5cGUgb2YgdmFsdWUgYXJlIHJlcXVpcmVkIGJ5IHRoZSBnZW9tIG9iamVjdC4KCkluIG1hbnkgY2FzZXMsIHlvdSB3aWxsIG5lZWQgdG8gY29udmVydCB0aGUgZGF0YSBmaXJzdC4KYG11dGF0ZSgpYCBmdW5jdGlvbiBhcmUgcXVpdGUgb2Z0ZW4gdXNlZCBmb3IgdGhhdC4KCmV4YW1wbGU6CmBgYHtyfQphbGxvd2FuY2UgPSBhbGxvd2FuY2UgJT4lIAogIG11dGF0ZShBc3Nlc3NtZW50X1llYXIgPSBhcy5udW1lcmljKHN1YnN0cihBc3Nlc3NtZW50X1llYXIsIDEgLDQpKSkgCmBgYAoKCiMjIFNpbXBsZSBMaW5lIFBsb3QKYGBge3IgQWxsb3dhbmNlIExpbmUgR3JhcGh9CiMgVGhlIGZvbGxvd2luZyBzdGF0ZW1lbnQgV09OJ1QgZ2VuZXJhdGUgYSBwbG90CmFsbG93YW5jZSAlPiUgZ2dwbG90KCkgKwogIGdlb21fbGluZShtYXBwaW5nID0gYWVzKHg9QXNzZXNzbWVudF9ZZWFyLCB5PUJhc2ljKSkKCiMgRm9yIGxpbmUgZ3JhcGhzLCB0aGUgZGF0YSBwb2ludHMgbXVzdCBiZSBncm91cGVkIHNvIHRoYXQgaXQga25vd3Mgd2hpY2ggcG9pbnRzIHRvIGNvbm5lY3QuIAojIEluIHRoaXMgY2FzZSwgYWxsIHBvaW50cyBzaG91bGQgYmUgY29ubmVjdGVkLCBzbyBncm91cD0xLiAKIyBXaGVuIG1vcmUgdmFyaWFibGVzIGFyZSB1c2VkIGFuZCBtdWx0aXBsZSBsaW5lcyBhcmUgZHJhd24sIHRoZSBncm91cGluZyBmb3IgbGluZXMgaXMgdXN1YWxseSBkb25lIGJ5IHZhcmlhYmxlLgphbGxvd2FuY2UgJT4lIGdncGxvdCgpICsKICBnZW9tX2xpbmUobWFwcGluZyA9IGFlcyh4PUFzc2Vzc21lbnRfWWVhciwgeT1CYXNpYywgZ3JvdXA9MSwgY29sb3I9Ik9yYW5nZSIpKQpgYGAKCiMjIEFkZGluZyBNdWx0aXBsZSBMYXllcnMgb2YgR2VvbWV0cnkKYGBge3J9CmFsbG93YW5jZSAlPiUgZ2dwbG90KCkgKwogIGdlb21fbGluZShtYXBwaW5nID0gYWVzKHg9QXNzZXNzbWVudF9ZZWFyLCB5PUJhc2ljLCBncm91cD0xLCBjb2xvcj0iT3JhbmdlIikpICsKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeD1Bc3Nlc3NtZW50X1llYXIsIHk9QmFzaWMsIGdyb3VwPTEsIGNvbG9yPSJPcmFuZ2UiKSkKCiMgQXMgYm90aCBnZW9tIHVzZSB0aGUgc2FtZSBkYXRhIG1hcHBpbmcsIHRoZSBhYm92ZSBzdGF0ZW1lbnRzIGNhbiBiZSBzaW1wbGlmaWVkIGFzCmFsbG93YW5jZSAlPiUgZ2dwbG90KGFlcyh4PUFzc2Vzc21lbnRfWWVhciwgeT1CYXNpYywgZ3JvdXA9MSwgY29sb3I9Ik9yYW5nZSIpKSArCiAgZ2VvbV9saW5lKCkgKwogIGdlb21fcG9pbnQoc2l6ZT01KQpgYGAKCiMjIFVzZSBnZW9tX3Ntb290aCgpIHRvIHNtb290aCBvdXQgdGhlIGxpbmUKYGBge3J9CmFsbG93YW5jZSAlPiUgZ2dwbG90KGFlcyh4PUFzc2Vzc21lbnRfWWVhciwgeT1CYXNpYywgZ3JvdXA9MSwgY29sb3I9Ik9yYW5nZSIpKSArCiAgZ2VvbV9zbW9vdGgoKSArCiAgZ2VvbV9wb2ludChzaXplPTUpCmBgYAoKCgojIyBTYXZlIGEgUGxvdDogZ2dzYXZlKCkKYGBge3J9Cm15LmZpcnN0LnBsb3QgPSBhbGxvd2FuY2UgJT4lIAogIGdncGxvdCgpICsgCiAgZ2VvbV9wb2ludCgKICAgIG1hcHBpbmc9YWVzKHg9QXNzZXNzbWVudF9ZZWFyLCB5PUJhc2ljKSwKICAgIGNvbG9yID0gIm9yYW5nZSIsCiAgICBzaXplID0gMywKICAgICkgCgpwcmludChteS5maXJzdC5wbG90KQoKZ2dzYXZlKCIuL291dHB1dC9teV9maXJzdF9wbG90LnBuZyIpICMgZGVmYXVsdCBpbWFnZSBzaXplCmdnc2F2ZSgiLi9vdXRwdXQvbXlfZmlyc3RfcGxvdF9sYXJnZS5wbmciLCB3aWR0aD00MDAwLCBoZWlnaHQ9MjAwMCwgdW5pdD0icHgiKQoKYGBgCgojIyBCYXIgQ2hhcnQgd2l0aCBnZW9tX2NvbCgpCgpgYGB7cn0KYWxsb3dhbmNlICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9jb2wobWFwcGluZz1hZXMoeD1Bc3Nlc3NtZW50X1llYXIsIHk9QmFzaWMpLAogICAgICAgICAgIGZpbGw9InRvbWF0byIpIApgYGAKCgoKCiMjIGdlb21fYmFyKCkKZ2VvbV9iYXIoKSBpcyB1c2VkIGZvciBjb3VudGluZyB0aGUgZnJlcXVlbmN5IG9mIGVhY2ggb2NjdXJyZW5jZSBvZiBvYnNlcnZlZCB2YWx1ZS4KSXQncyB1c3VhbGx5IGZvciBjb3VudGluZyBhIGxpbWl0IHNldCBvZiB2YWx1ZXMKCmBgYHtyfQphbGxvd2FuY2UgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2JhcihtYXBwaW5nPWFlcyh4PUJhc2ljKSkgIApgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyBDSEFMTEVOR0UKIyMgTXVsdGlwbGUgTGF5ZXJzIG9mIExpbmVzCmFkZCBsaW5lIHBsb3QgZm9yIGNvbHVtbiBvZiAnQ2hpbGQnCmluIHRoZSBzYW1lIHBsb3QsIGFkZCBhbm90aGVyIGxpbmUgcGxvdCBmb3IgJ0RlcGVuZGVudF9QYXJlbnRfNjAnCmBgYHtyfQphbGxvd2FuY2UgJT4lIGdncGxvdCgpICsKICBnZW9tX2xpbmUobWFwcGluZyA9IGFlcyh4PUFzc2Vzc21lbnRfWWVhciwgeT1DaGlsZCwgZ3JvdXA9MSksIGNvbG9yPSJPcmFuZ2UiKSArIAogIGdlb21fbGluZShtYXBwaW5nID0gYWVzKHg9QXNzZXNzbWVudF9ZZWFyLCB5PURlcGVuZGVudF9QYXJlbnRfNjAsIGdyb3VwPTEpLCBjb2xvcj0iQmx1ZSIpIApgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyBXT1JLIFdJVEggTU9SRSBDT01QTEVYIERBVEEKCiMjIExvYWRpbmcgRGF0YTogZ3JhZHVhdGVzLmNzdgpgYGB7ciByZWFkaW5nIGNvbXBsZXggZGF0YX0KZ3JhZHVhdGVzID0gcmVhZF9jc3YoIi4vZGF0YS9ncmFkdWF0ZXMuY3N2IikKcHJpbnQoZ3JhZHVhdGVzKQpgYGAKCiMjIFNpbXBsZSBTY2F0dGVycGxvdApMZXQncyBleHBsb3JlIHRoZSBkYXRhIHdpdGggc29tZSBzaW1wbGUgZ2dwbG90IHBsb3QuIApKdXN0IHNvbWUgcXVpY2sgZXhwbG9yYXRpb24uIFRoZXkgbWlnaHQgbm90IGJlIHZlcnkgdXNlZnVsLiAgCgpgYGB7cn0KZ2dwbG90KGRhdGE9Z3JhZHVhdGVzKSArCiAgZ2VvbV9wb2ludChtYXBwaW5nPWFlcyh4PUFjYWRlbWljWWVhciwgeT1IZWFkY291bnQpKQoKZ2dwbG90KGRhdGE9Z3JhZHVhdGVzKSArCiAgZ2VvbV9wb2ludChtYXBwaW5nPWFlcyh4PUFjYWRlbWljWWVhciwgeT1IZWFkY291bnQsIHNoYXBlPVNleCkgIyB1c2Ugc2hhcGUgdG8gZGlmZmVyZW50aWF0ZSBncm91cHMKICAgICAgICAgICAgICkKCmdncGxvdChkYXRhPWdyYWR1YXRlcykgKwogIGdlb21fcG9pbnQobWFwcGluZz1hZXMoeD1BY2FkZW1pY1llYXIsIHk9SGVhZGNvdW50LCBjb2xvcj1TZXgpICMgdXNlIGNvbG9yIHRvIGRpZmZlcmVudGlhdGUgZ3JvdXBzCiAgICAgICAgICAgICApCmBgYAoKIyMgVXNlIGZpbHRlcigpIHRvIEV4dHJhY3QgUmVxdWlyZWQgUm93cwpgYGB7ciB1c2UgZmlsdGVyKCl9CmdyYWR1YXRlcyAlPiUgCmZpbHRlcihMZXZlbE9mU3R1ZHk9PSJVbmRlcmdyYWR1YXRlIiwgUHJvZ3JhbW1lQ2F0ZWdvcnk9PSJCdXNpbmVzcyBhbmQgTWFuYWdlbWVudCIpICU+JQpnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChtYXBwaW5nPWFlcyh4PUFjYWRlbWljWWVhciwgeT1IZWFkY291bnQsIGNvbG9yPVNleCkKKQpgYGAKCgojIyBVc2UgTGluZSBQbG90IFRvIEV4cGxvcmUgdGhlIFRyZW5kaW5nIApVc2UgbGluZSBwbG90IHRvIGV4cGxvcmUgdGhlIHRyZW5kaW5nIG9mICJCdXNpbmVzcyBhbmQgTWFuYWdlbWVudCIgc3R1ZGVudCBoZWFkY291bnQgdHJlbmRpbmcKCgpgYGB7cn0KbGlicmFyeShtYWdyaXR0cikKZ3JhZHVhdGVzICU8PiUKICBtdXRhdGUoQWNhZGVtaWNZZWFyPWFzLmZhY3RvcihBY2FkZW1pY1llYXIpLAogICAgICAgICBTZXg9YXMuZmFjdG9yKFNleCkKICAgICAgICAgKSAjIGNvbnZlcnQgdGhlICdBY2FkZW1pY1llYXInIGFuZCBTZXggdG8gZmFjdG9yIHR5cGUKCmdyYWR1YXRlcyAlPiUgCmZpbHRlcihMZXZlbE9mU3R1ZHk9PSJVbmRlcmdyYWR1YXRlIiwgUHJvZ3JhbW1lQ2F0ZWdvcnk9PSJCdXNpbmVzcyBhbmQgTWFuYWdlbWVudCIpICU+JQpnZ3Bsb3QoKSArCiAgZ2VvbV9saW5lKAogICAgbWFwcGluZz1hZXMoeD1BY2FkZW1pY1llYXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgeT1IZWFkY291bnQsCiAgICAgICAgICAgICAgICAgICAgICAgICBncm91cD1TZXgsIAogICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9U2V4CiAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICApCgpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyBDSEFMTEVOR0UKIyMgQ29tcGFyaXNvbiB3aXRoIExpbmUgUGxvdHMKVXNlIGxpbmUgcGxvdHMgdG8gY29tcGFyZSBmZW1hbGUgdW5kZXJncmFkdWF0ZSBzdHVkZW50cyBoZWFkY291bnQgdHJlbmRpbmcgaW4gIlByb2dyYW1tZUNhdGVnb3J5IiBvZiAiQnVzaW5lc3MgYW5kIE1hbmFnZW1lbnQiIGFuZCAiRW5naW5lZXJpbmcgYW5kIFRlY2hub2xvZ3kiClVzZSBmaWx0ZXIoKSB0byBleHRyYWN0IHJlcXVpcmVkIHJlY29yZHMKWW91IGNhbiB1c2UgbXVsdGlwbGUgYGZpbHRlcigpYCBjYWxsClVzZSAmLCB8IG9yIG11bHRpcGxlIGNvbmRpdGlvbnMKCgpgYGB7ciBQcmV2aWV3aW5nIFByb2dyYW1DYXRlZ29yeX0KZ3JhZHVhdGVzICU+JSAKICAuJFByb2dyYW1tZUNhdGVnb3J5ICU+JSAjIC4gbWVhbnMgdGhlIHBhcmFtZXRlciBmcm9tIHByZXZpb3VzIGNvbW1hbmQKICB1bmlxdWUoKSAjIGRpc3BsYXkgdGhlIHVuaXF1ZSBuYW1lcyBvZiBQcm9ncmFtbWVDYXRlZ29yeQoKZ3JhZHVhdGVzICU+JSAKICBmaWx0ZXIoTGV2ZWxPZlN0dWR5PT0iVW5kZXJncmFkdWF0ZSIsIFNleD09IkYiKSAlPiUKICBmaWx0ZXIoUHJvZ3JhbW1lQ2F0ZWdvcnk9PSJCdXNpbmVzcyBhbmQgTWFuYWdlbWVudCIgfCBQcm9ncmFtbWVDYXRlZ29yeT09IkVuZ2luZWVyaW5nIGFuZCBUZWNobm9sb2d5IikgJT4lIAogIHByaW50KCkgIyBUZXN0IGV4dHJhY3RpbmcgYW5kIHByaW50aW5nIHRoZSByZXF1aXJlZCByZWNvcmRzLgpgYGAKCgpgYGB7ciBCdXNpbmVzcyBhbmQgTWFuYWdlbWVudCB2cy4gRW5naW5lZXJpbmcgYW5kIFRlY2hub2xvZ3l9CmdyYWR1YXRlcyAlPiUgCiAgZmlsdGVyKExldmVsT2ZTdHVkeT09IlVuZGVyZ3JhZHVhdGUiLCBTZXg9PSJGIikgJT4lCiAgZmlsdGVyKFByb2dyYW1tZUNhdGVnb3J5PT0iQnVzaW5lc3MgYW5kIE1hbmFnZW1lbnQiIHwgUHJvZ3JhbW1lQ2F0ZWdvcnk9PSJFbmdpbmVlcmluZyBhbmQgVGVjaG5vbG9neSIpICU+JSAKICBnZ3Bsb3QoCiAgICBhZXMoeD1BY2FkZW1pY1llYXIsIAogICAgICAgICAgICAgeT1IZWFkY291bnQsCiAgICAgICAgICAgICBncm91cD1Qcm9ncmFtbWVDYXRlZ29yeSwgCiAgICAgICAgICAgICBjb2xvcj1Qcm9ncmFtbWVDYXRlZ29yeQogICAgICAgICAgICAgKQogICAgKSArCiAgICBnZW9tX2xpbmUoKSArCiAgICBnZW9tX3BvaW50KCkgCiAgCmBgYAoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKIyBDSEFMTEVOR0U6IGxpbmUgcGxvdCBmb3IgaGlib3JfZml4aW5nXzFtCmBgYHtyfQpsaWJyYXJ5KGpzb25saXRlKSAjIGxvYWQgcGFja2FnZQpoa21hLmludGVyYmFuay51cmwgPSAiaHR0cHM6Ly9hcGkuaGttYS5nb3YuaGsvcHVibGljL21hcmtldC1kYXRhLWFuZC1zdGF0aXN0aWNzL2RhaWx5LW1vbmV0YXJ5LXN0YXRpc3RpY3MvZGFpbHktZmlndXJlcy1pbnRlcmJhbmstbGlxdWlkaXR5IgppbnRlcmJhbmsubGlxdWlkaXR5ID0gZnJvbUpTT04oaGttYS5pbnRlcmJhbmsudXJsKQojIHRoZSBhYm92ZSByZXRyaWV2YWwgd2lsbCB0YWtlIGEgd2hpbGUuICBUaGUgc2VydmVyIHJlc3BvbnNlIGlzIHNsb3cuCnN1bW1hcnkoaW50ZXJiYW5rLmxpcXVpZGl0eSkKc3RyKGludGVyYmFuay5saXF1aWRpdHkpCmludGVyYmFuay5saXF1aWRpdHkkcmVzdWx0CnN0cihpbnRlcmJhbmsubGlxdWlkaXR5JHJlc3VsdCkKaW50ZXJiYW5rLnJlY29yZHMgPSBpbnRlcmJhbmsubGlxdWlkaXR5JHJlc3VsdCRyZWNvcmRzICU+JSBhc190aWJibGUoKQppbnRlcmJhbmsucmVjb3JkcwoKaW50ZXJiYW5rLnJlY29yZHMgJT4lIApnZ3Bsb3QoKSArCiAgZ2VvbV9saW5lKAogICAgbWFwcGluZz1hZXMoeD1lbmRfb2ZfZGF0ZSwgeT1oaWJvcl9maXhpbmdfMW0sIGdyb3VwPTEpLAogICAgY29sb3I9Im9yYW5nZSIKICAgICAgICAgICAgICkKCmBgYAoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKCiMgR1JPVVBJTkcgQU5EIEFHR1JFR0FUSU9OCgojIyBVc2luZyBncm91cF9ieSgpIGFuZCBzdW1tYXJpc2UoKSAKYGBge3J9CmdyYWR1YXRlcyAlPiUgZ3JvdXBfYnkoQWNhZGVtaWNZZWFyLCBMZXZlbE9mU3R1ZHkpICU+JSAKICBzdW1tYXJpc2UoVG90YWxIZWFkY291bnQgPSBzdW0oSGVhZGNvdW50KSkgCgpncmFkdWF0ZXMgJT4lIGdyb3VwX2J5KEFjYWRlbWljWWVhciwgTGV2ZWxPZlN0dWR5KSAlPiUgCiAgc3VtbWFyaXNlKFRvdGFsSGVhZGNvdW50ID0gc3VtKEhlYWRjb3VudCkpICU+JSAKICBnZ3Bsb3QoCiAgICBhZXMoeD1BY2FkZW1pY1llYXIsIAogICAgICAgICAgICAgeT1Ub3RhbEhlYWRjb3VudCwKICAgICAgICAgICAgIGdyb3VwPUxldmVsT2ZTdHVkeSwgCiAgICAgICAgICAgICBjb2xvcj1MZXZlbE9mU3R1ZHkKICAgICAgICAgICAgICkKICAgICkgKwogICAgZ2VvbV9saW5lKCkgKwogICAgZ2VvbV9wb2ludCgpIAogIApgYGAKCiMjIFVzZSBvZiBmaWx0ZXIoKQpVc2UgYGZpbHRlcigpYCB0byBrZWVwIG9ubHkgIlRhdWdodCBQb3N0Z3JhZHVhdGUiIFJlY29yZHMKClRoaXMgcGxvdCBpcyBub3QgdmVyeSB1c2VmdWwgd2l0aG91dCBwcmV2aW91c2x5IGFwcGx5aW5nIGBmaWx0ZXIoKWAgYW5kIGBncm91cF9ieSgpYCBhbmQgYHN1bW1hcmlzZSgpYAoKYGBge3J9CmdyYWR1YXRlcyAlPiUgCiAgZmlsdGVyKExldmVsT2ZTdHVkeT09IlRhdWdodCBQb3N0Z3JhZHVhdGUiKSAlPiUgCiAgZ2dwbG90KCkgKwogICAgZ2VvbV9saW5lKG1hcHBpbmc9YWVzKHg9QWNhZGVtaWNZZWFyLHk9SGVhZGNvdW50LCBncm91cD1Qcm9ncmFtbWVDYXRlZ29yeSwgY29sb3I9UHJvZ3JhbW1lQ2F0ZWdvcnkpKQpgYGAKCgojIyBmaWx0ZXIoKSArIGdyb3VwX2J5KCkgKyBzdW1tYXJpc2UoKQpVc2UgYGZpbHRlcigpYCB0byBleHRyYWN0IHJlcXVpcmVkIHJvd3MKVXNlIGBncm91cF9ieSgpYCBhbmQgYHN1bW1hcmlzZSgpYCB0byBncm91cCBhbmQgYWdncmVnYXRlIHRvdGFsIGhlYWRjb3VudCBmb3IgYm90aCBtYWxlIGFuZCBmZW1hbGUKYGBge3IgZ2dwbG90IGxpbmV9CgpncmFkdWF0ZXMgJT4lIAogIGZpbHRlcihMZXZlbE9mU3R1ZHk9PSJUYXVnaHQgUG9zdGdyYWR1YXRlIikgJT4lIAogIGdyb3VwX2J5KEFjYWRlbWljWWVhciwgUHJvZ3JhbW1lQ2F0ZWdvcnkpICU+JSAKICBzdW1tYXJpc2UoVG90YWxIZWFkY291bnQgPSBzdW0oSGVhZGNvdW50KSkgJT4lIAogIGdncGxvdCgpICsKICAgIGdlb21fbGluZShtYXBwaW5nPWFlcyh4PUFjYWRlbWljWWVhcix5PVRvdGFsSGVhZGNvdW50LCBncm91cD1Qcm9ncmFtbWVDYXRlZ29yeSwgY29sb3I9UHJvZ3JhbW1lQ2F0ZWdvcnkpKQoKIyBGb2xsb3dpbmcgaXMgdGhlIHNhbWUgY2hhcnQgZm9yICJ1bmRlcmdyYWR1YXRlIiAKIyBncmFkdWF0ZXMgJT4lCiMgICBmaWx0ZXIoTGV2ZWxPZlN0dWR5PT0iVW5kZXJncmFkdWF0ZSIpICU+JQojICAgZ3JvdXBfYnkoQWNhZGVtaWNZZWFyLCBQcm9ncmFtbWVDYXRlZ29yeSkgJT4lCiMgICBzdW1tYXJpc2UoVG90YWxIZWFkY291bnQgPSBzdW0oSGVhZGNvdW50KSkgJT4lCiMgICBnZ3Bsb3QoKSArCiMgICAgIGdlb21fbGluZShtYXBwaW5nPWFlcyh4PUFjYWRlbWljWWVhcix5PVRvdGFsSGVhZGNvdW50LCBncm91cD1Qcm9ncmFtbWVDYXRlZ29yeSwgY29sb3I9UHJvZ3JhbW1lQ2F0ZWdvcnkpKQogICAgCmBgYAoKCgojIyBnZW9tX2NvbCgpIGZ1bmN0aW9uCgpgYGB7cn0KTGV2ZWxPZlN0dWR5ID0gZ3JhZHVhdGVzICU+JSAuJExldmVsT2ZTdHVkeSAlPiUgdW5pcXVlKCkKUHJvZ3JhbW1lQ2F0ZWdvcnkgPSBncmFkdWF0ZXMgJT4lIC4kUHJvZ3JhbW1lQ2F0ZWdvcnkgJT4lIHVuaXF1ZSgpIApwcmludChMZXZlbE9mU3R1ZHkpCnByaW50KFByb2dyYW1tZUNhdGVnb3J5KQogIApncmFkdWF0ZXMgJT4lIApmaWx0ZXIoUHJvZ3JhbW1lQ2F0ZWdvcnk9PSJCdXNpbmVzcyBhbmQgTWFuYWdlbWVudCIpICU+JSAKZ2dwbG90KCkgKwogIGdlb21fY29sKG1hcHBpbmc9YWVzKHg9QWNhZGVtaWNZZWFyLCB5PUhlYWRjb3VudCwgZmlsbD1MZXZlbE9mU3R1ZHkpKQoKZ3JhZHVhdGVzICU+JSAKZmlsdGVyKFByb2dyYW1tZUNhdGVnb3J5PT0iRW5naW5lZXJpbmcgYW5kIFRlY2hub2xvZ3kiKSAlPiUgCmdncGxvdCgpICsKICBnZW9tX2NvbChtYXBwaW5nPWFlcyh4PUFjYWRlbWljWWVhciwgeT1IZWFkY291bnQsIGZpbGw9TGV2ZWxPZlN0dWR5KSkKCmBgYAoKIyMgTW9yZSBBZ2dyZWdhdGlvbiBGdW5jdGlvbnMKQ2VudGVyOiBgbWVhbigpYCwgYG1lZGlhbigpYApTcHJlYWQ6IGBzZCgpYCwgYElRUigpYCwgYG1hZCgpYApSYW5nZTogYG1pbigpYCwgYG1heCgpYCwgYHF1YW50aWxlKClgClBvc2l0aW9uOiBgZmlyc3QoKWAsIGBsYXN0KClgLCBgbnRoKClgLApDb3VudDogYG4oKWAsIGBuX2Rpc3RpbmN0KClgCkxvZ2ljYWw6IGBhbnkoKWAsIGBhbGwoKWAKCk1vcmUgaW5mb3JtYXRpb24gYXQKW3N1bW1hcmlzZSgpIGZ1bmN0aW9uXShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3N1bW1hcmlzZS5odG1sKQoKCiMjIGdlb21fYmFyKCkgZnVuY3Rpb24KYmFyIGNoYXJ0IGdpdmUgdGhlIGNvdW50aW5nIGZyZXF1ZW5jeSAobnVtYmVyIG9mIHJlY29yZCBpbiB0aGUgZGF0YSBzZXQpCmBgYHtyfQpncmFkdWF0ZXMgJT4lIApnZ3Bsb3QoKSArCiAgZ2VvbV9iYXIobWFwcGluZz1hZXMoeD1BY2FkZW1pY1llYXIpKSAjIHlvdSBvbmx5IG5lZWQgdG8gcHJvdmlkZSB0aGUgeCBheGlzCmBgYAoKIyMgYm94IHBsb3QKVGhlIGJveHBsb3QgY29tcGFjdGx5IGRpc3BsYXlzIHRoZSBkaXN0cmlidXRpb24gb2YgYSBjb250aW51b3VzIHZhcmlhYmxlLlxuCkl0IHZpc3VhbGlzZXMgZml2ZSBzdW1tYXJ5IHN0YXRpc3RpY3MgKHRoZSBtZWRpYW4sIHR3byBoaW5nZXMgYW5kIHR3byB3aGlza2VycyksIGFuZCBhbGwgIm91dGx5aW5nIiBwb2ludHMgaW5kaXZpZHVhbGx5LgoKYGBge3J9CmdyYWR1YXRlcyAlPiUgCmdncGxvdCgpICsKICBnZW9tX3BvaW50KG1hcHBpbmc9YWVzKHg9U2V4LCB5PUhlYWRjb3VudCkpCgpncmFkdWF0ZXMgJT4lIApnZ3Bsb3QoKSArCiAgZ2VvbV9ib3hwbG90KG1hcHBpbmc9YWVzKHg9TGV2ZWxPZlN0dWR5LCB5PUhlYWRjb3VudCkpCmBgYAoKIyMgZmFjZXRfd3JhcCgpCmZhY2V0cyBhcmUgdXNlZnVsIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4KSXQgc3BsaXQgeW91ciBwbG90IGludG8gc3VicGxvdCAoYS5rLmEgZmFjZXRzKSB0aGF0IGVhY2ggZGlzcGxheSBvbmUgc3Vic2V0IG9mIHRoZSBkYXRhLgoKZmFjZXRfd3JhcCgpIGxldHMgeW91IHdvcmsgd2l0aCBPTkUgZXh0cmEgdmFyaWFibGUgKGJlc2lkZXMgeCBhbmQgeSkuICAKRWFjaCBjYXRlZ29yaWNhbCB2YWx1ZSB3aWxsIHVzZWQgdG8gcHJvZHVjZSB0byBhIHN1YiBwbG90LgpXZSB1c2UgJ0xldmVsT2ZTdHVkeScgaGVyZS4gIFNpbmNlIHRoZXJlIGFyZSBGT1VSIGRpc3RpbmN0IHZhbHVlcywgeW91IHdpbGwgc2VlIEZPVVIgc3ViLXBsb3RzCmBgYHtyfQpncmFkdWF0ZXMgJT4lIGdncGxvdCgpICsKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeD1BY2FkZW1pY1llYXIsIHk9SGVhZGNvdW50KSkgKwogIGZhY2V0X3dyYXAofiBMZXZlbE9mU3R1ZHkpICMgWW91IHdpbGwgc2VlIEZPVVIgc3ViIHBsb3RzIGFzIHRoZXJlIGFyZSBGT1VSIGRpc3RpbmN0IHZhbHVlcyBmb3IgdGhpcyB2YXJpYWJsZS4KYGBgCgojIyBmYWNldF9ncmlkKCkKZmFjZXRfZ3JpZCgpIGxldHMgeW91IHdvcmsgd2l0aCBUV08gZXh0cmEgdmFyaWFibGVzIChiZXNpZGVzIHggYW5kIHkpLiAgCkVhY2ggY29tYmluYXRpb24gb2YgdGhlc2UgdHdvIHZhcmlhYmxlcycgdmFsdWUgYXJlIHVzZWQgdG8gcHJvZHVjZSB0byBhIHN1YiBwbG90LgoKWW91IHNob3VsZCBTRUUgMjggc3ViIHBsb3RzIGFzIHRoZXJlIGFyZSBGT1VSIGRpc3RpbmN0IHZhbHVlcyBmb3IgJ0xldmVsT2ZTdHVkeScgYW5kIDcgZGlzdGluY3QgdmFsdWVzIGZvciAnUHJvZ3JhbW1lQ2F0ZWdvcnknLgpgYGB7cn0KZ3JhZHVhdGVzICU+JSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHg9QWNhZGVtaWNZZWFyLCB5PUhlYWRjb3VudCkpICsKICBmYWNldF9ncmlkKExldmVsT2ZTdHVkeSB+IFByb2dyYW1tZUNhdGVnb3J5KQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyBNQUtFIElUIFBSRVRUWQpVc2Ugb2YgdGl0bGUsIGxhYmVsLCBiYWNrZ3JvdW5kIGNvbG9yIGFuZCB0aGVtZXMKCmBgYHtyfQojIGluIHRoaXMgZXhhbXBsZSB3ZSBzYXZlIHRoZSBwbG90IHRvIGEgdmFyaWFibGUgbmFtZSAnbGV2ZWwuYmFyLnBsb3QnIHNvIHRoYXQgd2UgY2FuIHVzZSBpdCBhZ2FpbiBhbmQgYWdhaW4uCmxldmVsLmJhci5wbG90ID0gZ3JhZHVhdGVzICU+JSAKZmlsdGVyKFByb2dyYW1tZUNhdGVnb3J5PT0iRW5naW5lZXJpbmcgYW5kIFRlY2hub2xvZ3kiKSAlPiUgCmdncGxvdCgpICsKICBnZW9tX2NvbChtYXBwaW5nPWFlcyh4PUFjYWRlbWljWWVhciwgeT1IZWFkY291bnQsIGZpbGw9TGV2ZWxPZlN0dWR5KSkKCiMgVG8gc2hvdyB0aGUgcGxvdCwganVzdCB1c2UgcHJpbnQoKSBmdW5jdGlvbiB3aXRoIHRoZSBwcmV2aW91cyBzYXZlZCBwbG90IHZhcmlhYmxlIGFzIHBhcmFtZXRlci4KcHJpbnQobGV2ZWwuYmFyLnBsb3QpCmBgYAoKCiMjIFBsb3QgQmFja2dyb3VuZAoqKmVsZW1lbnRfcmVjdCgpKiogaXMgYSBmdW5jdGlvbiB0byBnZW5lcmF0ZWQgcmVjdGFuZ2xlIGdlb21ldHJ5IGVsZW1lbnQuCllvdSBoYXZlIHRvIHNwZWNpZnkgdGhlICoqZmlsbCoqIHBhcmFtZXRlciBieSBjb2xvciBuYW1lIG9yIGhleCBjb2RlIGNvZGUgYnkgc3RyaW5nCgpQbG90IEJhY2tncm91bmQgcmVmZXJzIHRvIHRoZSBiaWcgYXJlYSBvZiBldmVyeXRoaW5nIHJlbGV2YW50IHRvIHRoZSBwbG90LgoKYGBge3J9CmxldmVsLmJhci5wbG90ICMgZGVmYXVsdCBzdHlsZQoKbGV2ZWwuYmFyLnBsb3QgKwogIHRoZW1lKHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsPSJvcmFuZ2UiKSkgIyBzdHlsaW5nIHRoZSBwbG90IGJhY2tncm91bmQKYGBgCgojIyBQYW5lbCBCYWNrZ3JvdW5kClBhbmVsIGJhY2tncm91bmQgcmVmZXJzIHRvIHRoZSBpbm5lciBhcmVhIG9mIHBsb3QuIEFyZWEgZm9yIHNob3dpbmcgaGVhZGVyLCBheGlzIGxhYmVsIGFuZCBsZWdlbmQgYXJlIE5PVCBpbmNsdWRlZC4KYGBge3J9CmxldmVsLmJhci5wbG90ICMgZGVmYXVsdCBzdHlsZQoKbGV2ZWwuYmFyLnBsb3QgKwogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD0ib3JhbmdlIikpICMgc3R5bGluZyB0aGUgcGFuZWwgYmFja2dyb3VuZApgYGAKCiMjIFJlbW92ZSBQbG90IGFuZCBQYW5lbCBCYWNrZ3JvdW5kCkluIHZpc3VhbCBkZXNpZ24sIGNvbG9yIGlzIGEgdmVyeSBwb3dlcmZ1bCB0b29sIHRvIGd1aWRlIHVzZXJzJyBhdHRlbnRpb24uIEJ1dCB5b3UgaGF2ZSB0byB1c2UgdGhlbSBjYXJlZnVsbHkuCgpUb28gbWFueSBjb2xvcnMgd2lsbCB1c3VhbGx5IGRvIHRoZSBvcHBvc2l0ZSAtIGNvbmZ1c2UgdGhlIGF1ZGllbmNlLgpNaW5pbWFsIGRlc2lnbiBpcyB0aGUgcmVjZW50IHRyZW5kLiAgRXNwZWNpYWxseSB0cnVlIHdoZW4gbWFueSBhcmUgdXNpbmcgc21hbGwgZGV2aWNlIGxpa2UgbW9iaWxlIHBob25lIG9yIHRhYmxldCBmb3IgZGF5LXRvLWRheSBjb21tdW5pY2F0aW9uLgoKSW4gdGhpcyBleGFtcGxlLCB3ZSBhcmUgcmVtb3ZpbmcgYm90aCBwbG90IGFuZCBwYW5lbCBiYWNrZ3JvdW5kIHRvIGFjaGlldmUgYSBjbGVhbiBkZXNpZ24uIEFmdGVyIGFsbCwgYmFja2dyb3VuZCBpcyBOT1QgdGhlIG1haW4gZGlzaC4gVmVyeSBvZnRlbiBiYWNrZ3JvdW5kIGNvbG9yIGNhdXNlcyBkaXN0cmFjdGlvbiB0byBncmFwaC4KCmBgYHtyfQpsZXZlbC5iYXIucGxvdCAjIGRlZmF1bHQgc3R5bGUKCmxldmVsLmJhci5wbG90ICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArICMgc3R5bGluZyB0aGUgcGFuZWwgYmFja2dyb3VuZCB0byBub25lCiAgdGhlbWUocGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArICMgc3R5bGluZyB0aGUgcGxvdCBiYWNrZ3JvdW5kIHRvIG5vbmUKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3I9ImdyZXkiKSkgIyBzdHlsaW5nIHRoZSBncmlkIGxpbmUgZm9yIHktYXhpcwpgYGAKCiMjIENoYW5nZSBMYWJlbCBmb3IgeC95IEF4aXMKYGBge3J9CmxldmVsLmJhci5wbG90ICMgZGVmYXVsdCBzdHlsZQoKbGV2ZWwuYmFyLnBsb3QgKwogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpICsgIyBzdHlsaW5nIHRoZSBwYW5lbCBiYWNrZ3JvdW5kIHRvIG5vbmUKICB0aGVtZShwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpICsgIyBzdHlsaW5nIHRoZSBwbG90IGJhY2tncm91bmQgdG8gbm9uZQogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShjb2xvcj0iZ3JleSIpKSArICMgc3R5bGluZyB0aGUgZ3JpZCBsaW5lIGZvciB5LWF4aXMKICB5bGFiKCJOdW1iZXIgb2YgU3R1ZGVudCIpICsgIyBMYWJlbCBmb3IgWSBheGlzCiAgeGxhYigiWWVhciIpICMgTGFiZWwgZm9yIFggYXhpcwpgYGAKCiMjIFJhdGF0ZSB0aGUgTGFiZSBUZXh0CmBgYHtyfQpsZXZlbC5iYXIucGxvdCAjIGRlZmF1bHQgc3R5bGUKCmxldmVsLmJhci5wbG90ICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArICMgc3R5bGluZyB0aGUgcGFuZWwgYmFja2dyb3VuZCB0byBub25lCiAgdGhlbWUocGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArICMgc3R5bGluZyB0aGUgcGxvdCBiYWNrZ3JvdW5kIHRvIG5vbmUKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3I9ImdyZXkiKSkgKyAjIHN0eWxpbmcgdGhlIGdyaWQgbGluZSBmb3IgeS1heGlzCiAgeWxhYigiTnVtYmVyIG9mIFN0dWRlbnQiKSArICMgTGFiZWwgZm9yIFkgYXhpcwogIHhsYWIoIlllYXIiKSArICMgTGFiZWwgZm9yIFggYXhpcwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUpKQpgYGAKCgoKIyMgQ2hhbmdlIEZpbGwgQ29sb3JzCmBgYHtyfQpsZXZlbC5iYXIucGxvdCAjIGRlZmF1bHQgc3R5bGUKCmxldmVsLmJhci5wbG90ICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArICMgc3R5bGluZyB0aGUgcGFuZWwgYmFja2dyb3VuZCB0byBub25lCiAgdGhlbWUocGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArICMgc3R5bGluZyB0aGUgcGxvdCBiYWNrZ3JvdW5kIHRvIG5vbmUKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3I9ImdyZXkiKSkgKyAjIHN0eWxpbmcgdGhlIGdyaWQgbGluZSBmb3IgeS1heGlzCiAgeWxhYigiTnVtYmVyIG9mIFN0dWRlbnQiKSArICMgTGFiZWwgZm9yIFkgYXhpcwogIHhsYWIoIlllYXIiKSArICMgTGFiZWwgZm9yIFggYXhpcwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCJwdXJwbGUiLCAib3JhbmdlIiwgImJsdWUiLCAidG9tYXRvIikpICMgdXNlIGMoKSBmdW5jdGlvbiB0byBzcGVjaWZ5IGNvbG9yIGxpc3QKYGBgCgojIyBTdHlsaW5nIFRoZSBMZWdlbmRzCmBgYHtyfQpsZXZlbC5iYXIucGxvdCAjIGRlZmF1bHQgc3R5bGUKCmxldmVsLmJhci5wbG90ICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArICMgc3R5bGluZyB0aGUgcGFuZWwgYmFja2dyb3VuZCB0byBub25lCiAgdGhlbWUocGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArICMgc3R5bGluZyB0aGUgcGxvdCBiYWNrZ3JvdW5kIHRvIG5vbmUKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3I9ImdyZXkiKSkgKyAjIHN0eWxpbmcgdGhlIGdyaWQgbGluZSBmb3IgeS1heGlzCiAgeWxhYigiTnVtYmVyIG9mIFN0dWRlbnQiKSArICMgTGFiZWwgZm9yIFkgYXhpcwogIHhsYWIoIlllYXIiKSArICMgTGFiZWwgZm9yIFggYXhpcwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0idG9wIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCJwdXJwbGUiLCAib3JhbmdlIiwgImJsdWUiLCAidG9tYXRvIiksCiAgICAgICAgICAgICAgICAgICAgZ3VpZGUgPSBndWlkZV9sZWdlbmQodGl0bGU9IkxldmVsIG9mIFN0dWR5IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwucG9zaXRpb24gPSAiYm90dG9tIikKICAgICAgICAgICAgICAgICAgICApICMgbW92ZSBsZWdlbmQgcG9zaXRpb24gdG8gdG9wIGFuZCBsYWJlbCBwb3NpdGlvbiB0byBib3R0b20gCiAgCmBgYAoKIyMgQWRkIFRpdGxlIGFuZCBTdWJ0aXRsZQpgYGB7cn0KbGV2ZWwuYmFyLnBsb3QgIyBkZWZhdWx0IHN0eWxlCgpsZXZlbC5iYXIucGxvdCArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgKyAjIHN0eWxpbmcgdGhlIHBhbmVsIGJhY2tncm91bmQgdG8gbm9uZQogIHRoZW1lKHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgKyAjIHN0eWxpbmcgdGhlIHBsb3QgYmFja2dyb3VuZCB0byBub25lCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG9yPSJncmV5IikpICsgIyBzdHlsaW5nIHRoZSBncmlkIGxpbmUgZm9yIHktYXhpcwogIHlsYWIoIk51bWJlciBvZiBTdHVkZW50IikgKyAjIExhYmVsIGZvciBZIGF4aXMKICB4bGFiKCJZZWFyIikgKyAjIExhYmVsIGZvciBYIGF4aXMKICB0aGVtZShsZWdlbmQucG9zaXRpb249InRvcCIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygicHVycGxlIiwgIm9yYW5nZSIsICJibHVlIiwgInRvbWF0byIpLAogICAgICAgICAgICAgICAgICAgIGd1aWRlID0gZ3VpZGVfbGVnZW5kKHRpdGxlPSJMZXZlbCBvZiBTdHVkeSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsLnBvc2l0aW9uID0gImJvdHRvbSIpCiAgICAgICAgICAgICAgICAgICAgKSArICMgbW92ZSBsZWdlbmQgcG9zaXRpb24gdG8gdG9wIGFuZCBsYWJlbCBwb3NpdGlvbiB0byBib3R0b20gCiAgZ2d0aXRsZSgiSG9uZyBLb25nIEhpZ2hlciBFZHVjYXRpb24gU3R1ZGVudCBIZWFkY291bnQiLCBzdWJ0aXRsZT0iMjAwOSAtIDIwMTkiKQpgYGAKCiMjIEFkZCBBbm5vdGF0aW9ucyBUZXh0cwpBZGQgZXh0cmEgdGV4dHMvc2hhcGUgdG8gZW5oYW5jZSB5b3VyIHZpc3VhbGl6YXRpb24KYGBge3J9CmxldmVsLmJhci5wbG90ICMgZGVmYXVsdCBzdHlsZQoKbGV2ZWwuYmFyLnBsb3QgKwogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpICsgIyBzdHlsaW5nIHRoZSBwYW5lbCBiYWNrZ3JvdW5kIHRvIG5vbmUKICB0aGVtZShwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpICsgIyBzdHlsaW5nIHRoZSBwbG90IGJhY2tncm91bmQgdG8gbm9uZQogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShjb2xvcj0iZ3JleSIpKSArICMgc3R5bGluZyB0aGUgZ3JpZCBsaW5lIGZvciB5LWF4aXMKICB5bGFiKCJOdW1iZXIgb2YgU3R1ZGVudCIpICsgIyBMYWJlbCBmb3IgWSBheGlzCiAgeGxhYigiWWVhciIpICsgIyBMYWJlbCBmb3IgWCBheGlzCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJ0b3AiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoInB1cnBsZSIsICJvcmFuZ2UiLCAiYmx1ZSIsICJ0b21hdG8iKSwKICAgICAgICAgICAgICAgICAgICBndWlkZSA9IGd1aWRlX2xlZ2VuZCh0aXRsZT0iTGV2ZWwgb2YgU3R1ZHkiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbC5wb3NpdGlvbiA9ICJib3R0b20iKQogICAgICAgICAgICAgICAgICAgICkgKyAjIG1vdmUgbGVnZW5kIHBvc2l0aW9uIHRvIHRvcCBhbmQgbGFiZWwgcG9zaXRpb24gdG8gYm90dG9tIAogIGdndGl0bGUoIkhvbmcgS29uZyBIaWdoZXIgRWR1Y2F0aW9uIFN0dWRlbnQgSGVhZGNvdW50Iiwgc3VidGl0bGU9IjIwMDkgLSAyMDIwIikgKyAKICBhbm5vdGF0ZSgidGV4dCIsIGxhYmVsPSJSZWNvcmRcbkhpZ2giLCB4PSIyMDE3LzE4IiwgeT01MzAwKSAjIHlvdSBjYW4gY2hhbmdlIHRleHQgcG9zaXRpb24gdmFsdWUgb2YgeCBhbmQgeSB0byBzZXQgdGhlIHRleHQgcG9zaXRpb24KYGBgCgojIyBBZGRpbmcgUmVmZXJlbmNlIExpbmVzCmBgYHtyfQpsZXZlbC5iYXIucGxvdCAjIGRlZmF1bHQgc3R5bGUKCmxldmVsLmJhci5wbG90ICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArICMgc3R5bGluZyB0aGUgcGFuZWwgYmFja2dyb3VuZCB0byBub25lCiAgdGhlbWUocGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArICMgc3R5bGluZyB0aGUgcGxvdCBiYWNrZ3JvdW5kIHRvIG5vbmUKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3I9ImdyZXkiKSkgKyAjIHN0eWxpbmcgdGhlIGdyaWQgbGluZSBmb3IgeS1heGlzCiAgeWxhYigiTnVtYmVyIG9mIFN0dWRlbnQiKSArICMgTGFiZWwgZm9yIFkgYXhpcwogIHhsYWIoIlllYXIiKSArICMgTGFiZWwgZm9yIFggYXhpcwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0idG9wIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCJwdXJwbGUiLCAib3JhbmdlIiwgImJsdWUiLCAidG9tYXRvIiksCiAgICAgICAgICAgICAgICAgICAgZ3VpZGUgPSBndWlkZV9sZWdlbmQodGl0bGU9IkxldmVsIG9mIFN0dWR5IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwucG9zaXRpb24gPSAiYm90dG9tIikKICAgICAgICAgICAgICAgICAgICApICsgIyBtb3ZlIGxlZ2VuZCBwb3NpdGlvbiB0byB0b3AgYW5kIGxhYmVsIHBvc2l0aW9uIHRvIGJvdHRvbSAKICBnZ3RpdGxlKCJIb25nIEtvbmcgSGlnaGVyIEVkdWNhdGlvbiBTdHVkZW50IEhlYWRjb3VudCIsIHN1YnRpdGxlPSIyMDA5IC0gMjAxOSIpICsgCiAgYW5ub3RhdGUoInRleHQiLCBsYWJlbD0iUmVjb3JkXG5IaWdoIiwgeD0iMjAxNy8xOCIsIHk9NTMwMCkgKyAjIHlvdSBjYW4gY2hhbmdlIHRleHQgcG9zaXRpb24gdmFsdWUgb2YgeCBhbmQgeSB0byBzZXQgdGhlIHRleHQgcG9zaXRpb24KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MzIwMCkgKyAjIGFkZHMgaG9yaXpvbnRhbCBsaW5lCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gIjIwMTcvMTgiLCBjb2xvcj0id2hpdGUiKSAjIGFkZHMgdmVydGljYWwgbGluZQogIApgYGAKCgojIyBVc2luZyBUaGVtZXMKYGBge3J9CmxldmVsLmJhci5wbG90ICMgZGVmYXVsdCBzdHlsZQoKbGV2ZWwuYmFyLnBsb3QgKwogIHRoZW1lX2J3KCkgIyBibGFjayBhbmQgd2hpdGUgdGhlbWUKCmxldmVsLmJhci5wbG90ICsKICB0aGVtZV9taW5pbWFsKCkgIyBibGFjayBhbmQgd2hpdGUgdGhlbWUKCmxldmVsLmJhci5wbG90ICsKICB0aGVtZV9kYXJrKCkgIyBibGFjayBhbmQgd2hpdGUgdGhlbWUKYGBgCgojIyAgTW9yZSAzcmQtcGFydHkgVGhlbWVzCkluc3RhbGwgKipnZ3RoZW1lcyoqIHBhY2thZ2UgdG8gdW5sb2NrIHdpZGVyIHNlbGVjdGlvbnMgb2YgdGhlbWVzLgoKYGBge3J9CmlmICghcmVxdWlyZSgicGFjbWFuIikpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpICMgY2hlY2sgaWYgcGFjbWFuIGFscmVhZHkgaW5zdGFsbGVkLiBJZiBub3QsIGluc3RhbGwgaXQuCnBhY21hbjo6cF9sb2FkKGdndGhlbWVzKQoKbGV2ZWwuYmFyLnBsb3QgIyBkZWZhdWx0IHN0eWxlCgpsZXZlbC5iYXIucGxvdCArCiAgdGhlbWVfZXhjZWwoKSArICMgRXhjZWwgVGhlbWUKICBnZ3RpdGxlKCJFeGNlbCBUaGVtZSIpCiAgCgpsZXZlbC5iYXIucGxvdCArCiAgdGhlbWVfd3NqKCkgKyAjIFdhbGwgU3RyZWV0IEpvdXJuYWwgVGhlbWUKICBnZ3RpdGxlKCJXYWxsIFN0cmVldCBKb3VybmFsIFRoZW1lIikKCmxldmVsLmJhci5wbG90ICsKICB0aGVtZV9lY29ub21pc3QoKSArICMgRWNvbm9taXN0IFRoZW1lCiAgZ2d0aXRsZSgiRWNvbm9taXN0IFRoZW1lIikKCmxldmVsLmJhci5wbG90ICsKICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKSArICMgRml2ZSBUaGlydHkgRWlnaHQKICBnZ3RpdGxlKCJGaXZlIFRoaXJ0eSBFaWdodCBUaGVtZSIpCgpgYGAKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgTU9SRSBSRVNPVVJDRVMgT04gZ2dwbG90MgoKIyMgb2ZmaWNpYWwgd2Vic2l0ZQpgYGB7cn0KYnJvd3NlVVJMKCJodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy8iKQpgYGAKCiMjIGV4dGVuc2lvbnMgZ2FsbGVyeQpgYGB7cn0KYnJvd3NlVVJMKCJodHRwczovL2V4dHMuZ2dwbG90Mi50aWR5dmVyc2Uub3JnL2dhbGxlcnkiKQpgYGAKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgTU9ERUxTCgohW1dvcmsgRm9sbG93IG9mIERhdGEgU2NpZW5jZV0oaHR0cHM6Ly9kMzN3dWJyZmtpMGw2OC5jbG91ZGZyb250Lm5ldC9lNWJmMmE4ZjRjNzg3YTEyZmFjYmMwYjQxOTFmYzgyYmQxOTJmNGM1LzRlNWQyL2RpYWdyYW1zL2RhdGEtc2NpZW5jZS1tb2RlbC5wbmcpCgpEYXRhIFNjaWVuY2UgaXMgY29tYmluYXRpb24gb2YgZWZmb3J0cyBhbmQgcmVzdWx0cyBvZiBwcm9ncmFtbWluZywgbWF0aGVtYXRpY3MgYW5kIGRvbWFpbiBleHBlcnRpc2UuICBBbW9uZyBhbGwsIG1hdGhlbWF0aWNzIGlzIHRoZSBmb3VuZGF0aW9uIG9mIG1vZGVscy4gIFdpdGggbW9kZWxzLCBkYXRhIHNjaWVudGlzdHMgbWFrZSBwcmVkaWN0aW9uczsgZGlzY292ZXIgaGlkZGVuIHBhdHRlcm5zOyBhbmQgY29uY2x1ZGUgaW5zaWdodHMuIAoKTW9kZWxpbmcgaXMgdXN1YWxseSBhbiBpdGVyYXRpdmUgcHJvY2VzcyBhbW9uZyBkYXRhIHRyYW5zZm9ybWF0aW9uLCBkYXRhIHZpc3VhbGl6YXRpb24sIGV4cGxvcmluZyB3aXRoIG1vZGVscyBhbmQgZml0dGluZy4KCiMjIFdoYXQgZXhhY3RseSBpcyBhIE1vZGVsPwoKSHVtYW4gYXJlIGdvb2QgaW4gZHJhd2luZyBjb25jbHVzaW9ucyBhbmQgcHJvdmlkaW5nIGluc2lnaHQgd2hpbGUgYXJlIE5PVCBnb29kIGluIGRpcmVjdGx5IGZhY2luZyBsYXJnZSBudW1iZXIgb2YgZGF0YSBhdHRyaWJ1dGVzIGFuZCBodWdlIHZvbHVtZSBvZiByYXcgZGF0YS4gIAoKIVtNb2RlbHMgRXhhbXBsZV0oaHR0cHM6Ly9kMzN3dWJyZmtpMGw2OC5jbG91ZGZyb250Lm5ldC9lMjhhNjZhZGY2ZThiMmQxMjdkYjhkM2FmOWFjOTkyYTJhYmI4N2NlLzQ3MzA4L21vZGVsLWJhc2ljc19maWxlcy9maWd1cmUtaHRtbC91bm5hbWVkLWNodW5rLTQ1LTEucG5nKQoKQSBtb2RlbCBpcyBtYXRoZW1hdGljcyBleHByZXNzaW9uIHRoYXQgcHJvdmlkZXMgYSBzaW1wbGUgbG93LWRpbWVuc2lvbmFsICoqc3VtbWFyeSoqIG9mIGEgZGF0YSBzZXQgc28gdGhhdCB3ZSBjYW4gZHJhdyBjb25jbHVzaW9uIGFuZCBldmVuIHByb3ZpZGUgaW5zaWdodHMuICAKCk1vZGVscyBvbmx5IHByb3ZpZGUgYXBwcm94aW1hdGlvbiAoTk9UIHRoZSBleGFjdCB0cnV0aCkuCgoKIyMgQmFzaWMgQ29uY2VwdHMgb2YgTW9kZWwKCkxldCdzIGRvIHNvbWUgc2ltcGxlIFIgY29kaW5nIHRvIHVuY292ZXIgdGhlIGJhc2ljIGNvbmNlcHQgb2YgbW9kZWwKCmBgYHtyIExvYWRpbmcgUmVxdWlyZWQgUGFja2FnZXMgfQppZiAoIXJlcXVpcmUoInBhY21hbiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iKSAjIGluc3RhbGwgcGFjbWFuCnBhY21hbjo6cF9sb2FkKHBhY21hbiwgdGlkeXZlcnNlLCBtb2RlbHIsIG1hZ3JpdHRyKSAjIGluc3RhbGwgKG9yIGxvYWQpIHJlcXVpcmVkIHBhY2thZ2VzCmBgYAoKCkxldCdzIHVzZSBhIHNpbXBsZSBidWlsdC1pbiBkYXRhIHNldCAqKnNpbTEqKiBmb3IgZXhwbG9yaW5nLgpJbiB0aGlzIHNpbXVsYXRpb24gZGF0YSwgeW91IGNhbiBzdHJvbmdseSBzZWUgdGhlIHBhdHRlcm4gd2l0aCB0aGUgaGVscCBvZiBzaW1wbGUgc2NhdHRlci1wbG90LgoKYGBge3IgVXNpbmcgc2ltMSBEYXRhIFNldH0KcF9kYXRhKG1vZGVscikgIyBkaXNwbGF5IGFsbCB0aGUgYnVpbHQtaW4gZGF0YSBzZXRzIG9mIG1vZGVscgpwcmludChoZWlnaHRzKQo/aGVpZ2h0cwpwcmludChzaW0xKSAjIGNvbnRhaW5zIHR3byBjb250aW51b3VzIHZhcmlhYmxlczogeCwgeQoKZ2dwbG90KHNpbTEsIGFlcyh4LCB5KSkgKwogIGdlb21fcG9pbnQoKQpgYGAKCiMjIEdlbmVyYXRpbmcgYSBSYW5kb20gTGluZWFyIE1vZGVsCkxpbmVhciBsaW5lIGFuZCBxdWFkcmF0aWMgY3VydmUgYXJlIHdpZGVseSB1c2VkIHRvIGV4cGxvcmUgdGhlIHJlbGF0aW9uIG9mIHR3byB2YXJpYWJsZXMuIExldCdzIHRha2UgbGluZWFyIG1vZGVsIGFzIHNpbXBsZSBleGFtcGxlIHRvIGdyYWIgdGhlIGVzc2VuY2Ugb2YgbW9kZWwuCgpBIGxpbmVhciBtb2RlbCBpcyBkZXNjcmliZWQgYXMKYHkgPSBhMSArIHggKiBhMmAKeCBhbmQgeSBhcmUgdGhlIHZhcmlhYmxlIGZyb20gZGF0YSBzZXQKYTEgYW5kIGEyIGFyZSBwYXJhbWV0ZXJzIHRoYXQgY2FuIHZhcnkgdG8gY2FwdHVyZSBkaWZmZXJlbnQgcGF0dGVybnMuCgpMZXQncyBnZW5lcmF0ZSBhIHJhbmRvbSB2YWx1ZSBvZiBgYTFgIGFzICoqaW50ZXJjZXB0KiogYW5kIGBhMmAgYXMgKipzbG9wZSoqLgpIZXJlLCB3ZSB1c2UgKipydW5pbmYoKSoqIHRvIGdlbmVyYXRlIHJhbmRvbSB1bmlmb3JtIGRpc3RyaWJ1dGVkIG51bWJlcgoKKipOb3RlKio6IApZb3UgbWlnaHQgaGF2ZSB0byByZXBlYXRpdmVseSBydW4gYSBmZXcgdGltZXMgYmVmb3JlIHlvdSBjYW4gc2VlIHRoZSB2aXN1YWxpemVkIHJhbmRvbSBtb2RlbCByZXByZXNlbnRlZCBieSBhIG9yYW5nZSBzdHJhaWdodCBsaW5lLgpgYGB7cn0KbW9kZWwgPSB0aWJibGUoCiAgYTEgPSBydW5pZigxLCAtMjAsIDQwKSwgIyByYW5kb20gaW50ZXJjZXB0IHZhbHVlIGJldHdlZW4gLTIwIHRvIDQwCiAgYTIgPSBydW5pZigxLCAtNSwgNSkgIyByYW5kb20gc2xvcCB2YWx1ZSBiZXR3ZWVuIC01IHRvIDUKKQojIHByaW50KG1vZGVsKSAjIHVuLWNvbW1lbnQgdG8gcHJpbnQgdGhlIHJhbmRvbSBtb2RlbAoKZ2dwbG90KHNpbTEsIGFlcyh4LHkpKSArCiAgZ2VvbV9wb2ludCgpICsgIyB0aGlzIHBsb3RzIGFsbCB0aGUgZGF0YQogIGdlb21fYWJsaW5lKGFlcyhpbnRlcmNlcHQgPSBhMSwgc2xvcGUgPSBhMiksIAogICAgICAgICAgICAgIGRhdGE9bW9kZWwsIAogICAgICAgICAgICAgIGNvbG9yPSJPcmFuZ2UiCiAgICAgICAgICAgICAgKSAjIHRoaXMgYWRkIHRoZSBzdHJhaWdodCBsaW5lLCBhLmsuYSwgb3VyIHJhbmRvbSBtb2RlbCBvbiB0b3AgdG8gZGF0YSBsYXllcgpgYGAKCiMjIEdlbmVyYXRpbmcgMjUwIFJhbmRvbSBNb2RlbHMgYXMgQ2FuZGlkYXRlIE1vZGVscwpUaGUgbnVtYmVyIG9mIHBvdGVudGlhbCBtb2RlbHMgYXJlIHVubGltaXRlZC4gIExldCdzIHRyeSB0byBnZW5lcmF0ZSAyNTAgcmFuZG9tIG9uZXMgYXMgY2FuZGlkYXRlIG1vZGVscy4gIApBbW9uZyB0aGVzZSAyNTAgbW9kZWxzLCBzb21lIGFyZSB2ZXJ5IGJhZCB0aGF0IHlvdSBjYW4ganVkZ2UgZXZlbiBieSBnbGFuY2luZy4gU29tZSBhcmUgbm90IGJhZCBidXQgd2UgZG9uJ3Qga25vdyB3aGljaCBvbmUgaXMgdGhlIGJlc3QgYW1vbmcgdGhlbS4KYGBge3J9Cm1vZGVscyA9IHRpYmJsZSgKICBhMSA9IHJ1bmlmKDI1MCwgLTIwLCA0MCksICMgMjUwIHJhbmRvbSBpbnRlcmNlcHQgdmFsdWVzIGJldHdlZW4gLTIwIHRvIDQwCiAgYTIgPSBydW5pZigyNTAsIC01LCA1KSAjIDI1MCByYW5kb20gc2xvcCB2YWx1ZXMgYmV0d2VlbiAtNSB0byA1CikKCiMgbGV0IGFkZCB0aGVzZSByYW5kb20gbGluZWFyIG1vZGVscyBhcyBvdmVybGF5IG9uIHRvcCBvZiBkYXRhIApnZ3Bsb3Qoc2ltMSwgYWVzKHgseSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fYWJsaW5lKGFlcyhpbnRlcmNlcHQgPSBhMSwgc2xvcGUgPSBhMiksIGRhdGE9bW9kZWxzLCBhbHBoYT0wLjIpCmBgYAoKCiMjIFNlbGVjdGluZyB0aGUgTW9zdCBGaXR0aW5nIFRlbiBNb2RlbHMKYGBge3J9CiMgdGhpcyBmdW5jdGlvbiBjYWxjdWxhdGVzIHRoZSBtb2RlbGVkIHkgdmFsdWUgb2YgZWFjaCBnaXZlbiAneCcgdmFsdWUKbW9kZWxlZF95ID0gZnVuY3Rpb24oYSwgZGF0YSkgewogIGFbMV0gKyBkYXRhJHggKiBhWzJdICMgYVsxXSBpcyB0aGUgaW50ZXJjZXB0IGFuZCBhWzJdIGlzIHRoZSBzbG9wZQp9CiMgbW9kZWxlZF95KGMoNywgMS41KSwgc2ltMSkgIyB0ZXN0LXJ1biBtb2RlbGVkX3kgZnVuY3Rpb24KCiMgdGhpcyBmdW5jdGlvbiBjYWxjdWxhdGVzIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIGFuIGFjdHVhbCB5IHZhbHVlIGFuZCB0aGUgcHJlZGljdGVkIHkgdmFsdWUgKG1vZGVsZWRfeSkKbWVhc3VyZV9kaXN0YW5jZSA9IGZ1bmN0aW9uKG1vZCwgZGF0YSkgewogIGRpZmYgPC0gZGF0YSR5IC0gbW9kZWxlZF95KG1vZCwgZGF0YSkgIyBtb2QgaXMgcmFuZG9tIGludGVyY2VwdCBhbmQgc2xvcGUgb2YgYSBjZXJ0YWluIG1vZGVsCiAgc3FydChtZWFuKGRpZmYgXiAyKSkgIyByb290LW1lYW4tc3F1YXJlZCBkZXZpYXRpb24gdG8gY29tcHV0ZSBvdmVyYWxsIGRpc3RhbmNlCn0KIyBtZWFzdXJlX2Rpc3RhbmNlKGMoNywgMSw1KSwgc2ltMSkgIyB0ZXN0LXJ1biBtZWFzdXJlX2Rpc3RhbmNlIGZ1bmN0aW9uCgojIHRoaXMgZnVuY3Rpb24gY2FsY3VsYXRlcyB0aGUgJ292ZXJhbGwnIGRpc3RhbmNlIGZvciBhIGdpdmVuIG1vZGVsIHdpdGggYTEgYXMgaW50ZXJjZXB0IGFuZCBhMiBhcyBzbG9wZQpzaW0xX2Rpc3QgPSBmdW5jdGlvbihhMSwgYTIpIHsKICBtZWFzdXJlX2Rpc3RhbmNlKGMoYTEsIGEyKSwgc2ltMSkgIyBhMSBpcyB0aGUgaW50ZXJjZXB0IG9mIGEgbW9kZWwgd2hpbGUgYTIgaXMgdGhlIHNsb3BlCn0KCiMgdXNlIG1hcDJfZGJsIChhIG1hcHBpbmcgZnVuY3Rpb24pIHRvIGEgbmV3IGNvbHVtbiBuYW1lZCAnZGlzdCcgdG8gZWFjaCByYW5kb20gbW9kZWwKbW9kZWxzICU8PiUgCiAgbXV0YXRlKGRpc3QgPSBwdXJycjo6bWFwMl9kYmwoYTEsIGEyLCBzaW0xX2Rpc3QpKQptb2RlbHMgIyBub3cgd2UgaGF2ZSBhbiBleHRyYSBjb2x1bW4gbmFtZWQgJ2Rpc3QnIGluIG91ciBtb2RlbHMKCiMgcGxvdHRpbmcgdGhlIGJlc3QgMTAgbW9kZWxzIGZyb20gdGhlIHJhbmtlZCBkaXN0YW5jZXMKZ2dwbG90KHNpbTEsIGFlcyh4LCB5KSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAyLCBjb2xvdXIgPSAiZ3JleTMwIikgKyAKICBnZW9tX2FibGluZSgKICAgIGFlcyhpbnRlcmNlcHQgPSBhMSwgc2xvcGUgPSBhMiwgY29sb3IgPSAtZGlzdCkgLCAKICAgIGRhdGEgPSBmaWx0ZXIobW9kZWxzLCByYW5rKGRpc3QpIDw9IDEwKSAjIFRvIHNob3cgb25seSB0aGUgYmVzdCA1LCBjaGFuZ2UgMTAgdG8gZml2ZQogICkKCmBgYAoKCiMjIFVzaW5nIGxtKCkgZnVuY3Rpb24KSWYgdGhlIHByZXZpb3VzIFIgY29kZXMgb24gY2hvb3NpbmcgYmVzdCAxMCBhbW9uZyAyNTAgcmFuZG9tIG1vZGVscyBhcmUgdG9vIG11Y2ggZGlnZXN0LiBJdCdzIGZpbmUuICBJdCdzIGp1c3QgZm9yIHlvdSB0byBmZWVsIHRoZSBwcm9jZXNzIGFuZCBlc3NlbmNlIG9mIG1vZGVscyBhbmQgZml0dGluZyBtb2RlbHMuCgpJbiBmYWN0LCBSIG1ha2VzIGxpbmVhciBtb2RlbCBmaXR0aW5nIGV4dHJlbWVseSBlYXN5IGJ5IGp1c3Qgb25lIHNpbmdsZSBsaW5lIG9mIGZ1bmN0aW9uIGNhbGxpbmcgdG8gdGhlICoqbG0oKSoqIGZ1bmN0aW9uIChhIGJ1aWx0LWluIGxpbmVhciBtb2RlbCBmaXR0aW5nIGZ1bmN0aW9uKQoKKipsbSgpKiogZmluZHMgdGhlIGNsb3Nlc3QgbW9kZWwgaW4gYSBzaW5nbGUgc3RlcCwgdXNpbmcgYSBzb3BoaXN0aWNhdGVkIGFsZ29yaXRobSB0aGF0IGludm9sdmVzIGdlb21ldHJ5LCBjYWxjdWx1cywgYW5kIGxpbmVhciBhbGdlYnJhLgoKKipsbSgpKiogaGFzIGEgc3BlY2lhbCB3YXkgdG8gc3BlY2lmeSB0aGUgbW9kZWwgZmFtaWx5OiBmb3JtdWxhcy4gCkZvcm11bGFzIGxvb2sgbGlrZSAqKnkgfiB4KiosIHdoaWNoIGxtKCkgd2lsbCB0cmFuc2xhdGUgdG8gYSBmdW5jdGlvbiBsaWtlIHkgPSBhMSArIGEyICogeApgYGB7cn0KIyByZWd1bGFyIGZ1bmN0aW9uIGNhbGxpbmcgbWFubmVyCnNpbTFfYXV0b19tb2RlbCA9IGxtKHkgfiB4LCBkYXRhID0gc2ltMSkgIyBmaW5kaW5nIHRoZSBvcHRpbWl6ZWQgbGluZWFyIG1vZGVsCiMgT3IgdXNpbmcgcGlwaW5nIGJlbG93CnNpbTFfYXV0b19tb2RlbCA9IHNpbTEgJT4lICBsbSh5IH4geCwgLikgIyBzaW5jZSBwaXBpbmcgYWx3YXlzIGFzc3VtZXMgb3V0cHV0cyBmcm9tIHByZXZpb3VzIGZ1bmN0aW9uIGNhbGwgd2lsbCBiZSB0aGUgZmlyc3QgcGFyYW1ldGVyIG9mIG5leHQgZnVuY3Rpb24sIGhlcmUgd2UgdXNlICIuIiB0byBpbmRpY2F0ZSBzaW0xIHdpbGwgYmUgZmVkIGFzIHNlY29uZCBwYXJhbWV0ZXIKCnByaW50KHNpbTFfYXV0b19tb2RlbCkgIyBwcmludCBvdXQgdGhlIGF1dG8gZ2VuZXJhdGVkIGxpbmVhciBtb2RlbApwcmludChzdW1tYXJ5KHNpbTFfYXV0b19tb2RlbCkpICMgcHJpbnQgb3V0IHRoZSBzdW1tYXJ5IG9mIHRoZSBnZW5lcmF0ZWQgbGluZWFyIG1vZGVsCgpzaW0xX2NvZWYgPSBjb2VmKHNpbTFfYXV0b19tb2RlbCkgIyByZXRyaWV2ZXMgbW9kZWwncyBpbnRlcmNlcHQgYW5kIHNsb3BlCnByaW50KHNpbTFfY29lZikKCiMgdmlzdWFsaXplIHRoZSBhdXRvIGdlbmVyYXRlZCBsaW5lYXIgbW9kZWwgb24gdG9wIG9mIHRoZSBzaW0xIGRhdGEKZ2dwbG90KHNpbTEsIGFlcyh4LCB5KSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAyLCBjb2xvdXIgPSAiZ3JleTMwIikgKyAKICBnZW9tX2FibGluZSgKICAgIGFlcyhpbnRlcmNlcHQgPSBzaW0xX2NvZWZbMV0sIHNsb3BlID0gc2ltMV9jb2VmWzJdKSAKICApCmBgYAoKTWFraW5nIFByZWRpY3Rpb24KYGBge3J9Cm5ldy5kYXRhID0gZGF0YS5mcmFtZSh4ID0gYygxLDIsMyw0LDUsNiw3LDgsOSwxMCkpCnByZWRpY3Qoc2ltMV9hdXRvX21vZGVsLCBuZXcuZGF0YSkKYGBgCgoKIyMgVXNpbmcgbG0oKSBmdW5jdGlvbiB3aXRoIGNhdGVnb3JpY2FsIGRhdGEKVXNpbmcgYGxtKClgIGZ1bmN0aW9uIG9uIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUgd2lsbCB1c2UgbWVhbiB2YWx1ZSBmb3IgZWFjaCBjYXRlZ29yeSBmb3IgcHJlZGljdGlvbi4KYGBge3J9CnBfZGF0YShtb2RlbHIpICMgc2hvd3MgaW5jbHVkZWQgZGF0YSBzZXRzIHdpdGggbW9kZWxyIHBhY2thZ2UKc2ltMiAlPiUgc3VtbWFyeSgpCnNpbTIgCm1vZGVsX2NhdCA9IHNpbTIgJT4lICBsbSh5fngsIC4pCnByaW50KG1vZGVsX2NhdCkKZ3JpZCA9IHNpbTIgJT4lIAogIGRhdGFfZ3JpZCh4KSAlPiUgCiAgYWRkX3ByZWRpY3Rpb25zKG1vZGVsX2NhdCkKZ3JpZAoKZ2dwbG90KHNpbTIsIGFlcyh4KSkgKyAKICBnZW9tX3BvaW50KGFlcyh5ID0geSkpICsKICBnZW9tX3BvaW50KGRhdGEgPSBncmlkLCBhZXMoeSA9IHByZWQpLCBjb2xvdXIgPSAicmVkIiwgc2l6ZSA9IDQpCmBgYAoKCmBgYHtyfQpzaW0zCmBgYAoKIyMgTXVsdGlwbGUgUmVncmVzc2lvbgpNdWx0aXBsZSByZWdyZXNzaW9uIGlzIGFuIGV4dGVuc2lvbiBvZiBsaW5lYXIgcmVncmVzc2lvbiBpbnRvIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG1vcmUgdGhhbiB0d28gdmFyaWFibGVzLgpgYGB7cn0KP210Y2FycwpwcmludChtdGNhcnMpCm0uci5tb2RlbCA9IG10Y2FycyAlPiUgbG0obXBnfmRpc3AraHArd3QsIC4pCnByaW50KG0uci5tb2RlbCkKCm5ldy5kYXRhLjIgPSBkYXRhLmZyYW1lKAogIGRpc3AgPSBjKDIyMSksCiAgaHAgPSBjKDEwMiksCiAgd3QgPSBjKDIuOTEpCiAgKSAlPiUgcHJpbnQoKQoKcHJlZGljdChtLnIubW9kZWwsIG5ldy5kYXRhLjIpCmBgYAoKCgojIyBMb2dpc3RpYyBSZWdyZXNzaW9uCmBgYHtyfQpwcmludChtdGNhcnMpCmJpLm1vZGVsID0gbXRjYXJzICU+JSBnbG0oYW1+Y3lsK2hwK3d0LCAuLCBmYW1pbHk9Ymlub21pYWwpCnByaW50KGJpLm1vZGVsKQoKbmV3LmRhdGEuMyA9IGRhdGEuZnJhbWUoCiAgY3lsID0gYyg0KSwKICBocCA9IGMoMTAyKSwKICB3dCA9IGMoMy45MSkKICApICU+JSBwcmludCgpCnByZWRpY3QoYmkubW9kZWwsIG5ldy5kYXRhLjMpCgpuZXcuZGF0YS4zYiA9IGRhdGEuZnJhbWUoCiAgY3lsID0gYyg0LDYsOCksCiAgaHAgPSBjKDEwMiwxMTAsOTgpLAogIHd0ID0gYygyLjkxLCAyLjYyLCAzLjQ0KQogICkgJT4lIHByaW50KCkKc3VtbWFyeShiaS5tb2RlbCkKcHJlZGljdChiaS5tb2RlbCwgbmV3LmRhdGEuM2IpCmBgYAoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKIyBWSVNVQUxJWkFUSU9OIFZTLiBNT0RFTApWaXN1YWxpemF0aW9uIGlzIHVzdWFsbHkgdGhvdWdodCBhcyBhIHRvb2wgZm9yIGh5cG90aGVzaXMgZ2VuZXJhdGlvbiB0byBleHBsb3JlIHRoZSBoaWRkZW4gcGF0dGVybnMgYW1vbmcgZGF0YSB3aGlsZSBtb2RlbGxpbmcgaXMgdXN1YWxseSB0aG91Z2h0IGFzIGF0IHRvb2wgZm9yIGh5cG90aGVzaXMgY29uZmlybWF0aW9uICh0byBjb25maXJtIHdoYXQgYXJlIGZvdW5kIGJ5IGRhdGEgdmlzdWFsaXphdGlvbiB0b29scykuICBUaGVzZSB0d28gdG9vbHMgYXJlIHN1Z2dlc3RlZCB0byB1c2VkIGluIGFuIGl0ZXJhdGl2ZSBtYW5uZXIgc28gYXMgdG8gYWNoaWV2ZSBhIGRlZXBlciByZXZlYWxpbmcgb24gZGF0YS4KCgojIEZVUlRIRVIgTEVBUk5JTkcgUkVTT1VSQ0VTCiMjIE1vcmUgb24gZ2dwbG90MgoKIyMjIGdncGxvdDIgdHV0b3JpYWwKYGBge3J9CmJyb3dzZVVSTCgiaHR0cHM6Ly93d3cudHV0b3JpYWxzcG9pbnQuY29tL2dncGxvdDIvZ2dwbG90Ml9pbnRyb2R1Y3Rpb24uaHRtIikKYGBgCgpnZ3Bsb3QyOiBFbGVnYW50IEdyYXBoaWNzIGZvciBEYXRhIEFuYWx5c2lzIChXZWItYmFzZWQgRS1ib29rKQohW0VsZWdhbnQgR3JhcGhpY3MgZm9yIERhdGEgQW5hbHlzaXNdKGh0dHBzOi8vZ2dwbG90Mi1ib29rLm9yZy9jb3Zlci5qcGcpCiMjIwpgYGB7cn0KYnJvd3NlVVJMKCJodHRwczovL2dncGxvdDItYm9vay5vcmcvIikKYGBgCgoKIyMgUiBTdGF0aXNjYWwgTW9kZWxzCgojIyMgbW9yZSBvbiBtb2RlbCBidWlsZGluZwpBIGV4ZWNlbGxlbnQgZWJvb2sgZm9yIFIgRGF0YSBTY2llbmNlCiogQmFzaWMgQ29uY2VwdCBvZiBNb2RlbAoqIE1vZGVsIEJ1aWxkaW5nCiogRXhhbXBsZSBvbiBNb2RlbApBIHdlYiBiYXNlZCBlYm9vayBhdAohW1IgZm9yIERhdGEgU2NpZW5jZV0oaHR0cHM6Ly9kMzN3dWJyZmtpMGw2OC5jbG91ZGZyb250Lm5ldC9iODhlZjkyNmEwMDRiMGZjZTcyYjI1MjZiMGI1YzQ0MTM2NjZhNGNiLzI0YTMwL2NvdmVyLnBuZykKYGBge3J9CmJyb3dzZVVSTCgiaHR0cHM6Ly9yNGRzLmhhZC5jby5uei9tb2RlbC1pbnRyby5odG1sIikKYGBgCgojIyMgZXhhbXBsZXMgb24gbW9yZSBtb2RlbHMKKiBMaW5lYXIgUmVncmVzc2lvbgoqIE11bHRpcGxlIFJlZ3Jlc3Npb24KKiBMb2dpc3RpYyBSZWdyZXNzaW9uCiogRGVjaXNpb24gVHJlZQoqIFJhbmRvbSBGb3Jlc3QKKiBBbmQgc28gb24KYGBge3J9CmJyb3dzZVVSTCgiaHR0cHM6Ly93d3cudHV0b3JpYWxzcG9pbnQuY29tL3Ivcl9saW5lYXJfcmVncmVzc2lvbi5odG0iKQpgYGAKCiMjIFIgU3R1ZGlvIENoZWF0c2hlZXRzCk1hbnkgcXVpY2sgc3ludGF4IHJlZmVyZW5jZSBmb3IgUiBwcm9ncmFtbWluZyBhbmQgMy1yZCBwYWNrYWdlcwohW1IgU3R1ZGlvIENoZWF0c2hlZXRzXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9tYWluL3BuZ3MvZGF0YS12aXN1YWxpemF0aW9uLnBuZykKYGBge3J9CmJyb3dzZVVSTCgiaHR0cHM6Ly93d3cucnN0dWRpby5jb20vcmVzb3VyY2VzL2NoZWF0c2hlZXRzLyIpCmBgYAoKIyMgQWR2YW5jZWQgUgohW0FkdmFuY2VkIFJdKGh0dHBzOi8vZDMzd3VicmZraTBsNjguY2xvdWRmcm9udC5uZXQvNTY1OTE2MTk4YjBiZTUxYmY4OGIzNmY5NGI4MGM3ZWE2N2NhZmU3Yy83ZjcwYi9jb3Zlci5wbmcpCmBgYHtyfQpicm93c2VVUkwoImh0dHBzOi8vYWR2LXIuaGFkbGV5Lm56L2luZGV4Lmh0bWwiKQpgYGAKCg==